OpenTelemetry Traceparent HTTP Header [Java]
What is traceparent header?
The traceparent
HTTP header contains information about the incoming request in a distributed tracing system, for example:
# {version}-{trace-id}-{parent-id}-{trace-flags}
traceparent: 00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01
You can find the traceparent
header in HTTP responses using browser developer tools to extract trace IDs and locate specific traces in distributed tracing tools.
Using the header, you can extract a trace ID to find the trace in a one of distributed tracing tools. For example, from the header above, the trace ID is 80e1afed08e019fc1110464cfa66635c
.
Traceparent header format
The traceparent
header uses the version-trace_id-parent_id-trace_flags
format where:
version
is always00
trace_id
is a hex-encoded trace ID (16 bytes)span_id
is a hex-encoded span ID (8 bytes)trace_flags
is a hex-encoded 8-bit field containing tracing flags such as sampling
Automatic propagation
OpenTelemetry Java handles traceparent
headers automatically in most scenarios. When using the Java Agent or Spring Boot starter, HTTP client libraries automatically inject traceparent headers into outgoing requests, and server libraries automatically extract them from incoming requests.
Java Agent automatic propagation
# Download and run with Java Agent
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.service.name=my-service \
-jar my-application.jar
All HTTP requests will automatically include traceparent headers without code changes.
Manual propagation
When automatic instrumentation is not available, you can manually handle traceparent headers using OpenTelemetry's Propagators API.
Extracting from incoming requests
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.Scope;
import jakarta.servlet.http.HttpServletRequest;
public class TraceContextExtractor {
private final W3CTraceContextPropagator propagator = W3CTraceContextPropagator.getInstance();
public Context extractContext(HttpServletRequest request) {
return propagator.extract(
Context.current(),
request,
new HttpServletRequestGetter()
);
}
private static class HttpServletRequestGetter implements TextMapGetter<HttpServletRequest> {
@Override
public Iterable<String> keys(HttpServletRequest request) {
return Collections.list(request.getHeaderNames());
}
@Override
public String get(HttpServletRequest request, String key) {
return request.getHeader(key);
}
}
// Usage in a servlet or controller
public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
Context extractedContext = extractContext(request);
try (Scope scope = extractedContext.makeCurrent()) {
// Your business logic here with proper trace context
processRequest();
}
}
}
Injecting into outgoing requests
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapSetter;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.util.HashMap;
import java.util.Map;
public class TraceContextInjector {
private final W3CTraceContextPropagator propagator = W3CTraceContextPropagator.getInstance();
private final HttpClient httpClient = HttpClient.newHttpClient();
public void makeRequest(String url) {
Map<String, String> headers = new HashMap<>();
// Inject current trace context into headers
propagator.inject(
Context.current(),
headers,
new MapTextMapSetter()
);
// Build request with injected headers
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET();
// Add trace headers
headers.forEach(requestBuilder::header);
HttpRequest request = requestBuilder.build();
// Send request
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString());
}
private static class MapTextMapSetter implements TextMapSetter<Map<String, String>> {
@Override
public void set(Map<String, String> carrier, String key, String value) {
carrier.put(key, value);
}
}
}
Injecting into response headers
To include traceparent headers in HTTP responses, you can create a servlet filter:
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapSetter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class TraceparentResponseFilter implements Filter {
private final W3CTraceContextPropagator propagator = W3CTraceContextPropagator.getInstance();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
// Inject traceparent into response headers
if (response instanceof HttpServletResponse) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
SpanContext spanContext = Span.current().getSpanContext();
if (spanContext.isValid()) {
propagator.inject(
Context.current(),
httpResponse,
new HttpServletResponseSetter()
);
}
}
}
private static class HttpServletResponseSetter implements TextMapSetter<HttpServletResponse> {
@Override
public void set(HttpServletResponse response, String key, String value) {
response.setHeader(key, value);
}
}
}
Debugging propagation
Logging trace context
Log incoming traceparent headers and current span context for debugging:
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
public class TraceDebugFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(TraceDebugFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// Log incoming traceparent header
String incomingTraceparent = httpRequest.getHeader("traceparent");
logger.info("Incoming traceparent: {}", incomingTraceparent);
// Log current span context
SpanContext spanContext = Span.current().getSpanContext();
if (spanContext.isValid()) {
logger.info("Current trace context - TraceId: {}, SpanId: {}, Sampled: {}",
spanContext.getTraceId(),
spanContext.getSpanId(),
spanContext.isSampled());
} else {
logger.warn("No valid span context found");
}
}
chain.doFilter(request, response);
}
}
Validating format
Validate and parse traceparent headers to ensure they follow the W3C specification:
import java.util.regex.Pattern;
public class TraceparentValidator {
private static final Pattern TRACEPARENT_PATTERN =
Pattern.compile("^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$");
public static boolean isValidTraceparent(String traceparent) {
if (traceparent == null || traceparent.isEmpty()) {
return false;
}
return TRACEPARENT_PATTERN.matcher(traceparent).matches();
}
public static TraceparentInfo parseTraceparent(String traceparent) {
if (!isValidTraceparent(traceparent)) {
throw new IllegalArgumentException("Invalid traceparent format: " + traceparent);
}
String[] parts = traceparent.split("-");
return new TraceparentInfo(
parts[0], // version
parts[1], // traceId
parts[2], // spanId
parts[3] // flags
);
}
public static class TraceparentInfo {
private final String version;
private final String traceId;
private final String spanId;
private final String flags;
public TraceparentInfo(String version, String traceId, String spanId, String flags) {
this.version = version;
this.traceId = traceId;
this.spanId = spanId;
this.flags = flags;
}
// Getters
public String getVersion() { return version; }
public String getTraceId() { return traceId; }
public String getSpanId() { return spanId; }
public String getFlags() { return flags; }
public boolean isSampled() { return "01".equals(flags); }
}
}
Getting trace information
Access current trace context information and format it as traceparent header:
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
public class TraceInfoService {
public Map<String, Object> getTraceInfo() {
SpanContext spanContext = Span.current().getSpanContext();
Map<String, Object> traceInfo = new HashMap<>();
if (spanContext.isValid()) {
traceInfo.put("traceId", spanContext.getTraceId());
traceInfo.put("spanId", spanContext.getSpanId());
traceInfo.put("isSampled", spanContext.isSampled());
traceInfo.put("isRemote", spanContext.isRemote());
traceInfo.put("traceFlags", spanContext.getTraceFlags().asHex());
traceInfo.put("traceState", spanContext.getTraceState().asMap());
// Format as traceparent header
String traceparent = String.format("00-%s-%s-%s",
spanContext.getTraceId(),
spanContext.getSpanId(),
spanContext.getTraceFlags().asHex());
traceInfo.put("traceparent", traceparent);
} else {
traceInfo.put("error", "No valid span context available");
}
return traceInfo;
}
}
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.