google-site-verification: google61fe8ba583a51912.html
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