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:

text
# {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 always 00
  • 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

bash
# 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

java
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

java
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:

java
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:

java
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:

java
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:

java
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.

What's next?