W3C Trace Context in Java: Complete Guide for Distributed Tracing

Introduction to W3C Trace Context

W3C Trace Context is a standard that provides a unified format for propagating distributed tracing context across service boundaries. It defines standard HTTP headers (traceparent and tracestate) that carry trace information, enabling end-to-end tracing in microservices architectures.

Key Components

  • traceparent: Contains the trace ID, parent span ID, and trace flags
  • tracestate: Carries vendor-specific tracing information in a key-value format
  • Standard Format: Ensures interoperability across different tracing systems

Core Implementation

Dependencies

Add to your pom.xml:

<dependencies>
<!-- OpenTelemetry for tracing -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>1.34.1</version>
</dependency>
<!-- For HTTP client support -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
<!-- For JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- For servlet applications -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>

TraceParent Class

package com.example.tracecontext;
import java.util.Objects;
import java.util.regex.Pattern;
public class TraceParent {
private static final Pattern TRACE_PARENT_PATTERN = 
Pattern.compile("^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$");
private final String version;
private final String traceId;
private final String parentId;
private final String traceFlags;
public TraceParent(String version, String traceId, String parentId, String traceFlags) {
this.version = validateVersion(version);
this.traceId = validateTraceId(traceId);
this.parentId = validateSpanId(parentId);
this.traceFlags = validateTraceFlags(traceFlags);
}
public static TraceParent parse(String traceParentHeader) {
if (traceParentHeader == null || traceParentHeader.trim().isEmpty()) {
throw new IllegalArgumentException("traceparent header cannot be null or empty");
}
var matcher = TRACE_PARENT_PATTERN.matcher(traceParentHeader.trim());
if (!matcher.matches()) {
throw new IllegalArgumentException("Invalid traceparent format: " + traceParentHeader);
}
return new TraceParent(
matcher.group(1), // version
matcher.group(2), // trace-id
matcher.group(3), // parent-id
matcher.group(4)  // trace-flags
);
}
public static TraceParent create() {
String version = "00";
String traceId = generateRandomHex(32);  // 16 bytes
String parentId = generateRandomHex(16); // 8 bytes
String traceFlags = "01"; // Sampled
return new TraceParent(version, traceId, parentId, traceFlags);
}
public TraceParent createChild() {
String newParentId = generateRandomHex(16); // New span ID
return new TraceParent(this.version, this.traceId, newParentId, this.traceFlags);
}
private static String generateRandomHex(int length) {
var random = new java.security.SecureRandom();
byte[] bytes = new byte[length / 2];
random.nextBytes(bytes);
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
private String validateVersion(String version) {
if (!version.matches("^[0-9a-f]{2}$")) {
throw new IllegalArgumentException("Invalid version: " + version);
}
return version;
}
private String validateTraceId(String traceId) {
if (!traceId.matches("^[0-9a-f]{32}$")) {
throw new IllegalArgumentException("Invalid traceId: " + traceId);
}
if ("00000000000000000000000000000000".equals(traceId)) {
throw new IllegalArgumentException("TraceId cannot be all zeros");
}
return traceId;
}
private String validateSpanId(String spanId) {
if (!spanId.matches("^[0-9a-f]{16}$")) {
throw new IllegalArgumentException("Invalid spanId: " + spanId);
}
if ("0000000000000000".equals(spanId)) {
throw new IllegalArgumentException("SpanId cannot be all zeros");
}
return spanId;
}
private String validateTraceFlags(String traceFlags) {
if (!traceFlags.matches("^[0-9a-f]{2}$")) {
throw new IllegalArgumentException("Invalid traceFlags: " + traceFlags);
}
return traceFlags;
}
public boolean isSampled() {
// Check if sampled flag is set (bit 0 of traceFlags)
int flags = Integer.parseInt(traceFlags, 16);
return (flags & 0x01) == 1;
}
public void setSampled(boolean sampled) {
int flags = Integer.parseInt(traceFlags, 16);
if (sampled) {
flags |= 0x01; // Set sampled flag
} else {
flags &= ~0x01; // Clear sampled flag
}
this.traceFlags = String.format("%02x", flags);
}
public String toString() {
return String.format("%s-%s-%s-%s", version, traceId, parentId, traceFlags);
}
// Getters
public String getVersion() { return version; }
public String getTraceId() { return traceId; }
public String getParentId() { return parentId; }
public String getTraceFlags() { return traceFlags; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TraceParent that = (TraceParent) o;
return Objects.equals(version, that.version) &&
Objects.equals(traceId, that.traceId) &&
Objects.equals(parentId, that.parentId) &&
Objects.equals(traceFlags, that.traceFlags);
}
@Override
public int hashCode() {
return Objects.hash(version, traceId, parentId, traceFlags);
}
}

TraceState Class

package com.example.tracecontext;
import java.util.*;
import java.util.stream.Collectors;
public class TraceState {
private static final int MAX_KEYS = 32;
private static final Pattern KEY_PATTERN = Pattern.compile("^[a-z][_0-9a-z\\-*/]{0,255}$");
private static final Pattern VALUE_PATTERN = Pattern.compile("^[\\x20-\\x2B\\x2D-\\x7E]{0,255}$");
private final LinkedHashMap<String, String> entries;
public TraceState() {
this.entries = new LinkedHashMap<>();
}
public TraceState(String tracestateHeader) {
this();
if (tracestateHeader != null && !tracestateHeader.trim().isEmpty()) {
parseHeader(tracestateHeader.trim());
}
}
private void parseHeader(String tracestateHeader) {
String[] vendorEntries = tracestateHeader.split(",");
for (String entry : vendorEntries) {
String[] keyValue = entry.split("=");
if (keyValue.length != 2) {
throw new IllegalArgumentException("Invalid tracestate entry: " + entry);
}
String key = keyValue[0].trim();
String value = keyValue[1].trim();
put(key, value);
}
}
public TraceState put(String key, String value) {
validateKey(key);
validateValue(value);
// Remove existing entry if present (to move to front)
entries.remove(key);
// Check size limit
if (entries.size() >= MAX_KEYS) {
throw new IllegalStateException("TraceState cannot contain more than " + MAX_KEYS + " entries");
}
// Add to front (LinkedHashMap maintains insertion order)
LinkedHashMap<String, String> newEntries = new LinkedHashMap<>();
newEntries.put(key, value);
newEntries.putAll(entries);
entries.clear();
entries.putAll(newEntries);
return this;
}
public String get(String key) {
return entries.get(key);
}
public TraceState remove(String key) {
entries.remove(key);
return this;
}
public boolean containsKey(String key) {
return entries.containsKey(key);
}
public Set<String> keySet() {
return Collections.unmodifiableSet(entries.keySet());
}
public int size() {
return entries.size();
}
public boolean isEmpty() {
return entries.isEmpty();
}
private void validateKey(String key) {
if (key == null || key.isEmpty()) {
throw new IllegalArgumentException("Key cannot be null or empty");
}
if (!KEY_PATTERN.matcher(key).matches()) {
throw new IllegalArgumentException("Invalid key format: " + key);
}
}
private void validateValue(String value) {
if (value == null) {
throw new IllegalArgumentException("Value cannot be null");
}
if (!VALUE_PATTERN.matcher(value).matches()) {
throw new IllegalArgumentException("Invalid value format: " + value);
}
}
public String toString() {
return entries.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining(","));
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private TraceState traceState;
public Builder() {
this.traceState = new TraceState();
}
public Builder fromHeader(String tracestateHeader) {
this.traceState = new TraceState(tracestateHeader);
return this;
}
public Builder put(String key, String value) {
traceState.put(key, value);
return this;
}
public Builder remove(String key) {
traceState.remove(key);
return this;
}
public TraceState build() {
return traceState;
}
}
}

W3C Trace Context Propagator

package com.example.tracecontext;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.context.propagation.TextMapSetter;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
public class W3CTraceContextPropagator implements TextMapPropagator {
private static final String TRACE_PARENT_HEADER = "traceparent";
private static final String TRACE_STATE_HEADER = "tracestate";
private static final W3CTraceContextPropagator INSTANCE = new W3CTraceContextPropagator();
public static W3CTraceContextPropagator getInstance() {
return INSTANCE;
}
@Override
public Collection<String> fields() {
return Collections.unmodifiableList(
Arrays.asList(TRACE_PARENT_HEADER, TRACE_STATE_HEADER)
);
}
@Override
public <C> void inject(Context context, @Nullable C carrier, TextMapSetter<C> setter) {
if (context == null || setter == null) {
return;
}
W3CTraceContext w3cContext = context.get(W3CTraceContext.KEY);
if (w3cContext == null) {
return;
}
// Inject traceparent
if (w3cContext.getTraceParent() != null) {
setter.set(carrier, TRACE_PARENT_HEADER, w3cContext.getTraceParent().toString());
}
// Inject tracestate
if (w3cContext.getTraceState() != null && !w3cContext.getTraceState().isEmpty()) {
setter.set(carrier, TRACE_STATE_HEADER, w3cContext.getTraceState().toString());
}
}
@Override
public <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C> getter) {
if (context == null || getter == null) {
return context;
}
try {
String traceParentHeader = getter.get(carrier, TRACE_PARENT_HEADER);
String traceStateHeader = getter.get(carrier, TRACE_STATE_HEADER);
if (traceParentHeader == null) {
return context;
}
TraceParent traceParent = TraceParent.parse(traceParentHeader);
TraceState traceState = new TraceState(traceStateHeader);
W3CTraceContext w3cContext = new W3CTraceContext(traceParent, traceState);
return context.with(W3CTraceContext.KEY, w3cContext);
} catch (Exception e) {
// Log warning but don't fail extraction
System.err.println("Failed to extract W3C Trace Context: " + e.getMessage());
return context;
}
}
}

W3C Trace Context Container

package com.example.tracecontext;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
public class W3CTraceContext {
public static final ContextKey<W3CTraceContext> KEY = 
ContextKey.named("w3c-trace-context");
private final TraceParent traceParent;
private final TraceState traceState;
public W3CTraceContext(TraceParent traceParent, TraceState traceState) {
this.traceParent = traceParent;
this.traceState = traceState;
}
public W3CTraceContext() {
this(TraceParent.create(), new TraceState());
}
public static W3CTraceContext fromContext(Context context) {
W3CTraceContext w3cContext = context.get(KEY);
if (w3cContext == null) {
return new W3CTraceContext();
}
return w3cContext;
}
public Context storeInContext(Context context) {
return context.with(KEY, this);
}
public W3CTraceContext createChild() {
TraceParent childTraceParent = this.traceParent.createChild();
return new W3CTraceContext(childTraceParent, this.traceState);
}
// Getters
public TraceParent getTraceParent() { return traceParent; }
public TraceState getTraceState() { return traceState; }
public String getTraceId() {
return traceParent != null ? traceParent.getTraceId() : null;
}
public String getSpanId() {
return traceParent != null ? traceParent.getParentId() : null;
}
public boolean isSampled() {
return traceParent != null && traceParent.isSampled();
}
@Override
public String toString() {
return String.format("W3CTraceContext{traceParent=%s, traceState=%s}", 
traceParent, traceState);
}
}

HTTP Client Implementation

Trace-Aware HTTP Client

package com.example.tracecontext;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import java.io.IOException;
import java.util.Map;
public class TraceAwareHttpClient {
private final CloseableHttpClient httpClient;
private final W3CTraceContextPropagator propagator;
public TraceAwareHttpClient() {
this.httpClient = HttpClients.createDefault();
this.propagator = W3CTraceContextPropagator.getInstance();
}
public String get(String url, W3CTraceContext traceContext) throws IOException {
HttpGet request = new HttpGet(url);
injectTraceContext(request, traceContext);
return httpClient.execute(request, response -> {
return EntityUtils.toString(response.getEntity());
});
}
public String post(String url, String body, String contentType, 
W3CTraceContext traceContext) throws IOException {
HttpPost request = new HttpPost(url);
injectTraceContext(request, traceContext);
StringEntity entity = new StringEntity(body);
entity.setContentType(contentType);
request.setEntity(entity);
return httpClient.execute(request, response -> {
return EntityUtils.toString(response.getEntity());
});
}
private void injectTraceContext(HttpUriRequest request, W3CTraceContext traceContext) {
propagator.inject(
traceContext.storeInContext(io.opentelemetry.context.Context.current()),
request,
new TextMapSetter<HttpUriRequest>() {
@Override
public void set(HttpUriRequest carrier, String key, String value) {
carrier.setHeader(key, value);
}
}
);
// Also add as custom headers for debugging
request.setHeader("X-TraceId", traceContext.getTraceId());
request.setHeader("X-SpanId", traceContext.getSpanId());
}
public W3CTraceContext extractTraceContext(Map<String, String> headers) {
return propagator.extract(
io.opentelemetry.context.Context.current(),
headers,
new TextMapGetter<Map<String, String>>() {
@Override
public String get(Map<String, String> carrier, String key) {
return carrier.get(key);
}
@Override
public Iterable<String> keys(Map<String, String> carrier) {
return carrier.keySet();
}
}
).get(W3CTraceContext.KEY);
}
public void close() throws IOException {
httpClient.close();
}
}

Servlet Filter for HTTP Servers

package com.example.tracecontext;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class W3CTraceContextFilter implements Filter {
private final W3CTraceContextPropagator propagator;
public W3CTraceContextFilter() {
this.propagator = W3CTraceContextPropagator.getInstance();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, 
FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest)) {
chain.doFilter(request, response);
return;
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Extract trace context from incoming headers
W3CTraceContext incomingContext = extractContextFromRequest(httpRequest);
// Store in thread-local context
try (var scope = incomingContext.storeInContext(
io.opentelemetry.context.Context.current()).makeCurrent()) {
// Add trace IDs to response headers for client debugging
addTraceHeadersToResponse(httpResponse, incomingContext);
// Continue processing
chain.doFilter(request, response);
}
}
private W3CTraceContext extractContextFromRequest(HttpServletRequest request) {
Map<String, String> headers = new HashMap<>();
// Extract all headers
Collections.list(request.getHeaderNames())
.forEach(headerName -> 
headers.put(headerName, request.getHeader(headerName))
);
W3CTraceContext extractedContext = propagator.extract(
io.opentelemetry.context.Context.current(),
headers,
new TextMapGetter<Map<String, String>>() {
@Override
public String get(Map<String, String> carrier, String key) {
return carrier.get(key.toLowerCase());
}
@Override
public Iterable<String> keys(Map<String, String> carrier) {
return carrier.keySet();
}
}
).get(W3CTraceContext.KEY);
// If no context was extracted, create a new one
return extractedContext != null ? extractedContext : new W3CTraceContext();
}
private void addTraceHeadersToResponse(HttpServletResponse response, 
W3CTraceContext context) {
response.setHeader("X-TraceId", context.getTraceId());
response.setHeader("X-SpanId", context.getSpanId());
response.setHeader("X-TraceSampled", String.valueOf(context.isSampled()));
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Initialization logic if needed
}
@Override
public void destroy() {
// Cleanup logic if needed
}
}

Spring Boot Integration

Configuration Class

package com.example.tracecontext.config;
import com.example.tracecontext.W3CTraceContextFilter;
import com.example.tracecontext.W3CTraceContextPropagator;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@Configuration
public class TraceContextConfig {
@Bean
public W3CTraceContextPropagator w3cTraceContextPropagator() {
return W3CTraceContextPropagator.getInstance();
}
@Bean
public FilterRegistrationBean<W3CTraceContextFilter> traceContextFilter() {
FilterRegistrationBean<W3CTraceContextFilter> registrationBean = 
new FilterRegistrationBean<>();
registrationBean.setFilter(new W3CTraceContextFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
registrationBean.setName("W3CTraceContextFilter");
return registrationBean;
}
}

REST Controller with Trace Context

package com.example.tracecontext.controller;
import com.example.tracecontext.W3CTraceContext;
import com.example.tracecontext.W3CTraceContextPropagator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class TraceAwareController {
private static final Logger logger = LoggerFactory.getLogger(TraceAwareController.class);
private final W3CTraceContextPropagator propagator;
public TraceAwareController(W3CTraceContextPropagator propagator) {
this.propagator = propagator;
}
@GetMapping("/trace-info")
public ResponseEntity<Map<String, String>> getTraceInfo(HttpServletRequest request) {
// Extract trace context from request
W3CTraceContext traceContext = extractTraceContext(request);
// Log with trace context
logger.info("Processing trace-info request [traceId: {}, spanId: {}]", 
traceContext.getTraceId(), traceContext.getSpanId());
Map<String, String> traceInfo = new HashMap<>();
traceInfo.put("traceId", traceContext.getTraceId());
traceInfo.put("spanId", traceContext.getSpanId());
traceInfo.put("sampled", String.valueOf(traceContext.isSampled()));
traceInfo.put("traceParent", traceContext.getTraceParent().toString());
traceInfo.put("traceState", traceContext.getTraceState().toString());
return ResponseEntity.ok(traceInfo);
}
@PostMapping("/process")
public ResponseEntity<Map<String, Object>> processItem(
@RequestBody Map<String, Object> item,
HttpServletRequest request) {
W3CTraceContext traceContext = extractTraceContext(request);
// Create child context for this operation
W3CTraceContext childContext = traceContext.createChild();
logger.info("Processing item {} [traceId: {}, parentSpanId: {}, newSpanId: {}]", 
item.get("id"), childContext.getTraceId(), 
traceContext.getSpanId(), childContext.getSpanId());
// Simulate business processing
processBusinessLogic(item, childContext);
Map<String, Object> response = new HashMap<>();
response.put("status", "processed");
response.put("item", item);
response.put("traceId", childContext.getTraceId());
response.put("spanId", childContext.getSpanId());
// Add trace headers to response
HttpHeaders headers = new HttpHeaders();
headers.add("X-TraceId", childContext.getTraceId());
headers.add("X-SpanId", childContext.getSpanId());
return ResponseEntity.ok().headers(headers).body(response);
}
private W3CTraceContext extractTraceContext(HttpServletRequest request) {
Map<String, String> headers = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, request.getHeader(headerName));
}
return propagator.extract(
io.opentelemetry.context.Context.current(),
headers,
new TextMapGetter<Map<String, String>>() {
@Override
public String get(Map<String, String> carrier, String key) {
return carrier.get(key);
}
@Override
public Iterable<String> keys(Map<String, String> carrier) {
return carrier.keySet();
}
}
).get(W3CTraceContext.KEY);
}
private void processBusinessLogic(Map<String, Object> item, W3CTraceContext context) {
// Simulate some business logic
try {
Thread.sleep(100);
// Add custom trace state
context.getTraceState().put("custom-processor", "business-logic");
context.getTraceState().put("item-type", item.get("type").toString());
logger.debug("Completed business processing for item {}", item.get("id"));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("Business processing interrupted", e);
}
}
}

OpenTelemetry Integration

OpenTelemetry W3C Propagator Setup

package com.example.tracecontext.opentelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry extension.trace.propagation.W3CTraceContextPropagator;
public class OpenTelemetrySetup {
private static final String INSTRUMENTATION_NAME = "w3c-trace-context-demo";
public static OpenTelemetry initOpenTelemetry() {
// Use W3C Trace Context propagator
ContextPropagators propagators = ContextPropagators.create(
W3CTraceContextPropagator.getInstance()
);
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create()))
.setSampler(Sampler.alwaysOn())
.build();
return OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.setPropagators(propagators)
.buildAndRegisterGlobal();
}
public static void demonstrateTracing() {
OpenTelemetry openTelemetry = initOpenTelemetry();
Tracer tracer = openTelemetry.getTracer(INSTRUMENTATION_NAME);
// Create a span
Span span = tracer.spanBuilder("parent-operation").startSpan();
try (var scope = span.makeCurrent()) {
// The span is now the current span and W3C context is available
span.setAttribute("service.name", "demo-service");
// Extract current W3C context
Context currentContext = Context.current();
String traceId = Span.fromContext(currentContext).getSpanContext().getTraceId();
String spanId = Span.fromContext(currentContext).getSpanContext().getSpanId();
System.out.printf("Trace ID: %s, Span ID: %s%n", traceId, spanId);
// Simulate some work
doWork(tracer);
} finally {
span.end();
}
}
private static void doWork(Tracer tracer) {
// Create a child span
Span childSpan = tracer.spanBuilder("child-operation").startSpan();
try (var scope = childSpan.makeCurrent()) {
childSpan.setAttribute("work.type", "processing");
// Child span automatically inherits W3C context
String traceId = Span.current().getSpanContext().getTraceId();
String spanId = Span.current().getSpanContext().getSpanId();
System.out.printf("Child - Trace ID: %s, Span ID: %s%n", traceId, spanId);
} finally {
childSpan.end();
}
}
}

Testing W3C Trace Context

Unit Tests

package com.example.tracecontext;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TraceParentTest {
@Test
void testParseValidTraceParent() {
String validHeader = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
TraceParent traceParent = TraceParent.parse(validHeader);
assertEquals("00", traceParent.getVersion());
assertEquals("4bf92f3577b34da6a3ce929d0e0e4736", traceParent.getTraceId());
assertEquals("00f067aa0ba902b7", traceParent.getParentId());
assertEquals("01", traceParent.getTraceFlags());
assertTrue(traceParent.isSampled());
}
@Test
void testCreateNewTraceParent() {
TraceParent traceParent = TraceParent.create();
assertNotNull(traceParent.getTraceId());
assertNotNull(traceParent.getParentId());
assertEquals(32, traceParent.getTraceId().length());
assertEquals(16, traceParent.getParentId().length());
assertEquals("00", traceParent.getVersion());
}
@Test
void testCreateChild() {
TraceParent parent = TraceParent.create();
TraceParent child = parent.createChild();
assertEquals(parent.getTraceId(), child.getTraceId());
assertEquals(parent.getVersion(), child.getVersion());
assertEquals(parent.getTraceFlags(), child.getTraceFlags());
assertNotEquals(parent.getParentId(), child.getParentId());
}
@Test
void testSampledFlag() {
TraceParent traceParent = TraceParent.create();
traceParent.setSampled(true);
assertTrue(traceParent.isSampled());
traceParent.setSampled(false);
assertFalse(traceParent.isSampled());
}
}
class TraceStateTest {
@Test
void testParseTraceState() {
String header = "vendor1=value1,vendor2=value2";
TraceState traceState = new TraceState(header);
assertEquals("value1", traceState.get("vendor1"));
assertEquals("value2", traceState.get("vendor2"));
assertEquals(2, traceState.size());
}
@Test
void testTraceStateOrdering() {
TraceState traceState = new TraceState();
traceState.put("first", "value1");
traceState.put("second", "value2");
traceState.put("first", "value3"); // Update first
// The updated entry should move to front
String result = traceState.toString();
assertTrue(result.startsWith("first=value3"));
}
@Test
void testTraceStateValidation() {
TraceState traceState = new TraceState();
assertThrows(IllegalArgumentException.class, () -> {
traceState.put("Invalid Key", "value"); // Invalid key format
});
assertThrows(IllegalArgumentException.class, () -> {
traceState.put("validkey", "invalid\u0000value"); // Invalid value
});
}
}

Integration Test

package com.example.tracecontext;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.Map;
import static org.mockito.Mockito.*;
class W3CTraceContextFilterTest {
@Test
void testFilterAddsTraceContext() throws IOException, ServletException {
W3CTraceContextFilter filter = new W3CTraceContextFilter();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
// Add W3C headers to request
request.addHeader("traceparent", "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01");
request.addHeader("tracestate", "vendor1=value1");
filter.doFilter(request, response, filterChain);
// Verify response headers
assertNotNull(response.getHeader("X-TraceId"));
assertNotNull(response.getHeader("X-SpanId"));
verify(filterChain).doFilter(request, response);
}
@Test
void testFilterCreatesNewContextWhenMissing() throws IOException, ServletException {
W3CTraceContextFilter filter = new W3CTraceContextFilter();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
// No headers in request
filter.doFilter(request, response, filterChain);
// Verify response headers are still added
assertNotNull(response.getHeader("X-TraceId"));
assertNotNull(response.getHeader("X-SpanId"));
verify(filterChain).doFilter(request, response);
}
}

Best Practices and Patterns

1. Context Propagation in Async Operations

package com.example.tracecontext;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AsyncTraceContext {
private final ExecutorService executor;
public AsyncTraceContext() {
this.executor = Executors.newCachedThreadPool();
}
public CompletableFuture<String> processAsync(String data) {
// Capture current context
W3CTraceContext currentContext = W3CTraceContext.fromContext(
io.opentelemetry.context.Context.current()
);
return CompletableFuture.supplyAsync(() -> {
// Restore context in async thread
try (var scope = currentContext.storeInContext(
io.opentelemetry.context.Context.current()).makeCurrent()) {
// Now we have the trace context in the async thread
System.out.println("Processing async with trace: " + currentContext.getTraceId());
// Simulate work
Thread.sleep(100);
return "Processed: " + data;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}, executor);
}
}

2. Custom Trace State Management

package com.example.tracecontext;
public class CustomTraceStateManager {
public static void addUserContext(String userId) {
W3CTraceContext currentContext = W3CTraceContext.fromContext(
io.opentelemetry.context.Context.current()
);
if (currentContext != null) {
currentContext.getTraceState().put("user.id", userId);
}
}
public static void addFeatureFlag(String feature, boolean enabled) {
W3CTraceContext currentContext = W3CTraceContext.fromContext(
io.opentelemetry.context.Context.current()
);
if (currentContext != null) {
currentContext.getTraceState().put("feature." + feature, 
enabled ? "enabled" : "disabled");
}
}
public static String getUserId() {
W3CTraceContext currentContext = W3CTraceContext.fromContext(
io.opentelemetry.context.Context.current()
);
return currentContext != null ? 
currentContext.getTraceState().get("user.id") : null;
}
}

Conclusion

This comprehensive implementation of W3C Trace Context in Java provides:

  • Complete W3C Trace Context specification implementation
  • HTTP propagation for both clients and servers
  • Spring Boot integration for easy adoption
  • OpenTelemetry compatibility for existing tracing infrastructure
  • Async context propagation for modern applications
  • Comprehensive testing for reliability

Key benefits include standardized distributed tracing, interoperability across different systems and languages, and production-ready implementation with proper error handling and validation.

The implementation follows W3C specification precisely while providing practical utilities for real-world applications, making distributed tracing seamless across your microservices architecture.

Java Observability, Logging Intelligence & AI-Driven Monitoring (APM, Tracing, Logs & Anomaly Detection)

https://macronepal.com/blog/beyond-metrics-observing-serverless-and-traditional-java-applications-with-thundra-apm/
Explains using Thundra APM to observe both serverless and traditional Java applications by combining tracing, metrics, and logs into a unified observability platform for faster debugging and performance insights.

https://macronepal.com/blog/dynatrace-oneagent-in-java-2/
Explains Dynatrace OneAgent for Java, which automatically instruments JVM applications to capture metrics, traces, and logs, enabling full-stack monitoring and root-cause analysis with minimal configuration.

https://macronepal.com/blog/lightstep-java-sdk-distributed-tracing-and-observability-implementation/
Explains Lightstep Java SDK for distributed tracing, helping developers track requests across microservices and identify latency issues using OpenTelemetry-based observability.

https://macronepal.com/blog/honeycomb-io-beeline-for-java-complete-guide-2/
Explains Honeycomb Beeline for Java, which provides high-cardinality observability and deep query capabilities to understand complex system behavior and debug distributed systems efficiently.

https://macronepal.com/blog/lumigo-for-serverless-in-java-complete-distributed-tracing-guide-2/
Explains Lumigo for Java serverless applications, offering automatic distributed tracing, log correlation, and error tracking to simplify debugging in cloud-native environments. (Lumigo Docs)

https://macronepal.com/blog/from-noise-to-signals-implementing-log-anomaly-detection-in-java-applications/
Explains how to detect anomalies in Java logs using behavioral patterns and machine learning techniques to separate meaningful incidents from noisy log data and improve incident response.

https://macronepal.com/blog/ai-powered-log-analysis-in-java-from-reactive-debugging-to-proactive-insights/
Explains AI-driven log analysis for Java applications, shifting from manual debugging to predictive insights that identify issues early and improve system reliability using intelligent log processing.

https://macronepal.com/blog/titliel-java-logging-best-practices/
Explains best practices for Java logging, focusing on structured logs, proper log levels, performance optimization, and ensuring logs are useful for debugging and observability systems.

https://macronepal.com/blog/seeking-a-loguru-for-java-the-quest-for-elegant-and-simple-logging/
Explains the search for simpler, more elegant logging frameworks in Java, comparing modern logging approaches that aim to reduce complexity while improving readability and developer experience.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper