Solving the 'Broken Trace' Problem with OpenTelemetry Context Propagation
Stop dealing with fragmented traces. Learn how OpenTelemetry Context Propagation and W3C headers link microservices into a single, observable journey.
10 Mar 2026, 17:56 UTC

The Mystery of the Disappearing Trace
You have instrumented three microservices with OpenTelemetry. You can see individual spans for each service in your backend, but they aren't connected. Instead of one continuous journey from the gateway to the database, you have three separate, unrelated traces. This is a failure of Context Propagation.
The takeaway is simple: instrumentation only records what happens inside a process. To connect those processes, you must explicitly move the trace metadata (the Trace ID and Span ID) across the network. Without a shared propagation standard, your distributed system is just a collection of isolated logs.
How Propagation Actually Works
Context propagation relies on two primary mechanisms: Injectors and Extractors. When Service A calls Service B, the OpenTelemetry SDK uses an Injector to write the current trace context into the outgoing request headers. When Service B receives that request, an Extractor reads those headers and starts its own spans as children of the incoming context.
By default, OpenTelemetry uses the W3C Trace Context standard. This standard defines two specific HTTP headers:
traceparent: Contains the version, the unique Trace ID, the parent Span ID, and trace flags (like whether the request is sampled).tracestate: Carries vendor-specific information to ensure interoperability between different tracing tools.
Choosing Your Propagator: W3C vs. B3
While W3C is the modern default, you will often encounter B3 (originally from Zipkin). B3 uses headers like X-B3-TraceId and X-B3-SpanId.
The decision usually comes down to your legacy infrastructure. If you are using an older service mesh or a legacy load balancer that expects B3 headers to perform request routing or logging, you must configure a composite propagator that supports both. If you are building a greenfield project, stick to W3C to ensure compatibility with the wider ecosystem.
Worked Example: Configuring a Global Propagator
To ensure every outgoing request carries the trace context, you must set the GlobalTextMapPropagator during the SDK initialization phase. This configuration should happen once at the application entry point before any spans are created.
Example (Java/SDK):
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.W3CTraceContextPropagator;
// Run this during application startup with administrative permissions
GlobalOpenTelemetry.setGlobalTextMapPropagator(
ContextPropagators.create(W3CTraceContextPropagator.getInstance())
);
Verification Steps:
- Deploy two instrumented services locally.
- Use a tool like
curl -vor a proxy (e.g., Charles or Wireshark) to inspect the headers of the request sent from Service A to Service B. - Confirm the presence of the
traceparentheader. It should look like00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01. - Check your tracing backend to verify that the Trace ID in Service B matches the Trace ID in Service A.
The Danger of Baggage
OpenTelemetry also provides a Baggage API. While trace context identifies where a request is, Baggage allows you to pass custom key-value pairs (like customer_id or region) across the entire trace.
The Trade-off: Baggage is transmitted in the HTTP headers of every single downstream call. If you store large objects or too many keys in Baggage, you increase the network overhead and risk hitting header size limits on your load balancers or API gateways, which can lead to 431 Request Header Fields Too Large errors.
Practical Limitations
Context propagation is not magic; it is a relay race. If a single service in your chain is not instrumented or fails to propagate the headers, the trace is broken. Additionally, context is often lost when moving across asynchronous boundaries, such as handing a task off to a custom thread pool or a message queue, unless you manually wrap the runnable/callable to capture and restore the context.
Actionable Summary
To fix broken traces, verify that your GlobalTextMapPropagator is initialized at startup. If you are integrating with legacy systems, use a composite propagator to support both W3C and B3. Finally, use Baggage sparingly—only for high-value metadata that must be visible to every downstream service.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.