OpenTelemetry Sampling [Java]
What is sampling?
Sampling is a process that restricts the amount of traces that are generated by a system. In high-volume applications, collecting 100% of traces can be expensive and unnecessary. Sampling allows you to collect a representative subset of traces while reducing costs and performance overhead.
Java sampling
OpenTelemetry Java SDK provides head-based sampling capabilities where the sampling decision is made at the beginning of a trace. By default, the tracer provider uses a ParentBased sampler with the AlwaysOn sampler. A sampler can be set on the tracer provider using the setSampler() method.
Built-in samplers
AlwaysOn
Samples every trace. Useful for development environments but be careful in production with significant traffic:
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.samplers.Sampler;
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.setSampler(Sampler.traceIdRatioBased(1.0))
.build();
AlwaysOff
Samples no traces. Useful for completely disabling tracing:
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.setSampler(Sampler.traceIdRatioBased(0.0))
.build();
TraceIdRatioBased
Samples a fraction of spans based on the trace ID. The ratio should be between 0.0 and 1.0:
// Sample 10% of traces
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.setSampler(Sampler.traceIdRatioBased(0.1))
.build();
// Sample 50% of traces
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.setSampler(Sampler.traceIdRatioBased(0.5))
.build();
ParentBased
A sampler decorator that behaves differently based on the parent of the span. If the span has no parent, the decorated sampler is used to make the sampling decision:
// ParentBased with TraceIdRatioBased root sampler
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.setSampler(Sampler.parentBased(Sampler.traceIdRatioBased(0.1)))
.build();
// ParentBased with AlwaysOn root sampler (default behavior)
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.setSampler(Sampler.parentBased(Sampler.traceIdRatioBased(1.0)))
.build();
Configuration in Java
Environment variable
You can configure sampling using environment variables:
# TraceIdRatio sampler with 50% sampling
export OTEL_TRACES_SAMPLER="traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.5"
# ParentBased with TraceIdRatio
export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.1"
# Always sample
export OTEL_TRACES_SAMPLER="always_on"
# Never sample
export OTEL_TRACES_SAMPLER="always_off"
Programmatic configuration
package com.example;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
public class TracingSetup {
public static OpenTelemetry setupTracing() {
// Create OTLP exporter
OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint("https://api.uptrace.dev:4317")
.addHeader("uptrace-dsn", System.getenv("UPTRACE_DSN"))
.build();
// Create resource
Resource resource = Resource.getDefault()
.merge(Resource.builder()
.put(ResourceAttributes.SERVICE_NAME, "my-service")
.put(ResourceAttributes.SERVICE_VERSION, "1.0.0")
.build());
// Configure sampler based on environment
Sampler sampler;
String env = System.getenv("JAVA_ENV");
switch (env != null ? env : "default") {
case "development":
sampler = Sampler.traceIdRatioBased(1.0);
break;
case "production":
sampler = Sampler.parentBased(Sampler.traceIdRatioBased(0.1)); // 10% sampling
break;
default:
sampler = Sampler.parentBased(Sampler.traceIdRatioBased(0.25)); // 25% sampling
}
// Create tracer provider
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
.setResource(resource)
.setSampler(sampler)
.build();
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.build();
// Add shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
tracerProvider.close();
}));
return openTelemetry;
}
}
Custom sampler
Create custom sampling logic by implementing the Sampler interface:
package com.example;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import java.util.List;
public class CustomSampler implements Sampler {
private final Sampler defaultSampler;
public CustomSampler() {
this.defaultSampler = Sampler.traceIdRatioBased(0.1); // 10% default sampling
}
@Override
public SamplingResult shouldSample(
Context parentContext,
String traceId,
String name,
SpanKind spanKind,
Attributes attributes,
List<LinkData> parentLinks) {
// Always sample critical operations
if (name.contains("critical") ||
name.contains("payment") ||
name.contains("auth")) {
return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE);
}
// Don't sample health checks
if (name.contains("health") || name.contains("ping")) {
return SamplingResult.create(SamplingDecision.DROP);
}
// Use default sampler for other cases
return defaultSampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks);
}
@Override
public String getDescription() {
return "CustomSampler{critical=always,health=never,default=10%}";
}
}
// Usage
public class Main {
public static void main(String[] args) {
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.setSampler(new CustomSampler())
.build();
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.build();
}
}
Advanced scenarios
Attribute-based sampling
package com.example;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import java.util.List;
public class AttributeBasedSampler implements Sampler {
private final Sampler highPrioritySampler;
private final Sampler defaultSampler;
public AttributeBasedSampler() {
this.highPrioritySampler = Sampler.traceIdRatioBased(1.0);
this.defaultSampler = Sampler.traceIdRatioBased(0.05); // 5% default
}
@Override
public SamplingResult shouldSample(
Context parentContext,
String traceId,
String name,
SpanKind spanKind,
Attributes attributes,
List<LinkData> parentLinks) {
// Always sample admin routes
String httpRoute = attributes.get(AttributeKey.stringKey("http.route"));
if (httpRoute != null && httpRoute.startsWith("/admin")) {
return highPrioritySampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks);
}
// Always sample error responses
Long statusCode = attributes.get(AttributeKey.longKey("http.status_code"));
if (statusCode != null && statusCode >= 400) {
return highPrioritySampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks);
}
return defaultSampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks);
}
@Override
public String getDescription() {
return "AttributeBasedSampler{admin=always,errors=always,default=5%}";
}
}
Java production deployment
HTTP server with tracing
package com.example;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import com.sun.net.httpserver.HttpServer;
import java.net.InetSocketAddress;
public class HttpServerExample {
public static void main(String[] args) throws Exception {
OpenTelemetry openTelemetry = setupTracing();
Tracer tracer = openTelemetry.getTracer("my-http-server");
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
server.createContext("/hello", exchange -> {
Span span = tracer.spanBuilder("handle-request").startSpan();
try (Scope scope = span.makeCurrent()) {
if (span.isRecording()) {
span.setAttribute("http.method", exchange.getRequestMethod());
span.setAttribute("http.status_code", 200);
}
String response = "Hello, World!";
exchange.sendResponseHeaders(200, response.length());
exchange.getResponseBody().write(response.getBytes());
exchange.getResponseBody().close();
} finally {
span.end();
}
});
server.start();
System.out.println("Server started on port 8080");
}
private static OpenTelemetry setupTracing() {
OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint("https://api.uptrace.dev:4317")
.addHeader("uptrace-dsn", System.getenv("UPTRACE_DSN"))
.build();
Resource resource = Resource.getDefault()
.merge(Resource.builder()
.put(ResourceAttributes.SERVICE_NAME, "my-http-server")
.put(ResourceAttributes.SERVICE_VERSION, "1.0.0")
.build());
// Configure sampling based on environment
Sampler sampler = "production".equals(System.getenv("ENVIRONMENT"))
? Sampler.parentBased(Sampler.traceIdRatioBased(0.1))
: Sampler.traceIdRatioBased(1.0);
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
.setResource(resource)
.setSampler(sampler)
.build();
return OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.build();
}
}
Database operations
public class UserService {
private final Connection connection;
private final Tracer tracer;
public UserService(Connection connection, Tracer tracer) {
this.connection = connection;
this.tracer = tracer;
}
public void createUser(User user) throws SQLException {
Span span = tracer.spanBuilder("create-user").startSpan();
try (Scope scope = span.makeCurrent()) {
if (span.isRecording()) {
span.setAttribute("user.email", user.getEmail());
span.setAttribute("operation.result", "success");
}
String sql = "INSERT INTO users (email, name) VALUES (?, ?)";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, user.getEmail());
stmt.setString(2, user.getName());
stmt.executeUpdate();
}
} catch (SQLException e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, "Failed to create user");
throw e;
} finally {
span.end();
}
}
}
Monitoring sampling
Statistics collector
public class SamplingStatsCollector implements SpanProcessor {
private final AtomicLong totalSpans = new AtomicLong(0);
private final AtomicLong sampledSpans = new AtomicLong(0);
@Override
public void onStart(Context parentContext, ReadWriteSpan span) {
totalSpans.incrementAndGet();
if (span.getSpanContext().isSampled()) {
sampledSpans.incrementAndGet();
}
}
@Override
public boolean isStartRequired() {
return true;
}
@Override
public void onEnd(ReadableSpan span) {}
@Override
public boolean isEndRequired() {
return false;
}
public void logStats() {
long total = totalSpans.get();
long sampled = sampledSpans.get();
if (total > 0) {
double rate = (double) sampled / total * 100;
System.out.printf("Sampling rate: %.2f%% (%d/%d spans)%n", rate, sampled, total);
}
}
}
OpenTelemetry APM
Uptrace is a OpenTelemetry backend that supports distributed tracing, metrics, and logs. You can use it to monitor applications and troubleshoot issues.

Uptrace comes with an intuitive query builder, rich dashboards, alerting rules with notifications, and integrations for most languages and frameworks.
Uptrace can process billions of spans and metrics on a single server and allows you to monitor your applications at 10x lower cost.
In just a few minutes, you can try Uptrace by visiting the cloud demo (no login required) or running it locally with Docker. The source code is available on GitHub.