skip to content

How is trace context propagated between services, and what is the difference between W3C traceparent and B3 propagation formats in Micrometer Tracing?

level: seniorimportance: must knowfreq 60%

answer

  1. inject on the way out, extract on the way in
  2. W3C = one traceparent: version-trace-span-flags
  3. B3 = X-B3-* or single b3 header (Zipkin/Brave)
  4. management.tracing.propagation.type / produce / consume
  5. Boot 3 default W3C; Sleuth defaulted B3

basics

~20 s

The trace ID, span ID, and sample flag travel in HTTP headers. The caller injects them, the callee extracts them. W3C uses one traceparent header; B3 (from Zipkin) uses multiple X-B3-* headers or a single b3 header. You pick the format via configuration.

solid answer

~40 s

Trace context is serialized into request headers so a downstream service can continue the same trace. The instrumentation on the client side **injects** trace ID, parent span ID, and the sampling flag into outbound headers; the server side **extracts** them and creates a child span under the same trace ID. Two wire formats dominate: **W3C Trace Context**, a standard using a single `traceparent` header (`version-traceId-spanId-flags`) plus optional `tracestate`; and **B3**, Zipkin's original format using either multiple headers (`X-B3-TraceId`, `X-B3-SpanId`, `X-B3-ParentSpanId`, `X-B3-Sampled`) or a single compact `b3` header. In Micrometer Tracing you configure which formats to produce and consume via `management.tracing.propagation.type` (or `.produce`/`.consume`) with values like `W3C` or `B3`. The Boot 3 default is W3C. Mismatched formats between services silently break the trace into disconnected fragments.

code

yaml · 16 lines
yaml
# application.yml — accept BOTH formats during a fleet migration,
# but emit only W3C going forward.
management:
  tracing:
    sampling:
      probability: 1.0
    propagation:
      consume: [W3C, B3]   # read either format from upstream
      produce: [W3C]       # write W3C on outbound calls
    baggage:
      remote-fields: [tenantId]        # propagate this baggage over the wire
      correlation:
        fields: [tenantId]             # also copy it into MDC for logs

# Resulting outbound header example:
# traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

go deeper

for a junior

Know that trace context travels in HTTP headers.

for a middle

Name the W3C traceparent and B3 headers and the inject/extract roles.

for a senior

Configure produce/consume for mixed fleets and reason about the Boot2->Boot3 default flip.

for a principal

Set an org-wide propagation standard, plan phased migrations, and govern baggage/PII across trust boundaries.

## What must propagate To continue a trace across a network boundary, the callee needs at minimum: the **trace ID**, the **current span ID** (which becomes the callee's parent), and the **sampling decision** (whether this trace is being recorded). Optionally, **baggage** (arbitrary key-value context) travels too. This bundle is the **trace context**, and moving it over the wire is **context propagation**. ## Inject and extract Propagation is symmetric: - **Inject** (a.k.a. write): on the *outbound* side, client instrumentation (`RestTemplate`, `WebClient`, feign, Kafka producer, etc.) writes the context into carrier headers before the request leaves. - **Extract** (a.k.a. read): on the *inbound* side, server instrumentation reads those headers and rebuilds the context, then starts a child span. If either side is missing or uses a different format, propagation fails and you get **broken traces** — separate trace IDs on each side. ## W3C Trace Context A W3C standard (recommended default). One header: ``` traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 ``` Fields, dash-separated: **version** (`00`), **trace-id** (32 hex = 128-bit), **parent-id**/span-id (16 hex = 64-bit), **trace-flags** (`01` = sampled). A companion `tracestate` header carries vendor-specific state. Advantages: it is a formal W3C spec, interoperable across vendors, single header. ## B3 (Zipkin) propagation Older format from Zipkin/Brave. Two encodings: - **Multi-header**: `X-B3-TraceId`, `X-B3-SpanId`, `X-B3-ParentSpanId`, `X-B3-Sampled` (and `X-B3-Flags` for debug). - **Single header** `b3: {traceId}-{spanId}-{sampled}-{parentSpanId}`. B3 trace IDs can be 64-bit or 128-bit. Many existing Zipkin deployments and the Brave library speak B3 natively. ## Configuring it in Spring Boot 3 Properties under `management.tracing.propagation`: - `management.tracing.propagation.type` = `W3C` or `B3` (sets both produce and consume). - `management.tracing.propagation.produce` and `.consume` accept a list, so you can, for example, **accept both W3C and B3** while **emitting W3C** during a migration. The **default in Boot 3 is W3C**. Sleuth (Boot 2) historically defaulted to B3, which is a classic upgrade gotcha. ## Bridge interplay (Brave vs OpenTelemetry) Both bridges support both formats, but the underlying propagator implementation differs. With the Brave bridge, propagation uses Brave's `Propagation`; with the OTel bridge, it uses OpenTelemetry's `TextMapPropagator`. The Spring properties abstract this, but if you hand-roll propagators you work against the bridge's own API. ## Baggage Beyond trace/span IDs you can propagate **baggage** — user-defined key-values (e.g. `tenantId`) that ride along the whole trace. Configure remote baggage fields and, if desired, mirror them into MDC via `management.tracing.baggage.remote-fields` and `.correlation.fields`. Baggage has a cost: it is copied onto every outbound request, so keep it small. ## Common gotchas - **Format mismatch across a fleet**: service A emits W3C, service B only reads B3 -> B splits off a new trace. During migrations, configure `consume` to accept both. - **Boot 2 -> Boot 3 upgrade**: default flips from B3 to W3C; a mixed fleet breaks correlation until aligned. - **Non-instrumented clients** (raw `HttpURLConnection`, some gRPC setups) never inject headers, silently dropping context. - **Proxies stripping headers**: a gateway that drops unknown `X-B3-*`/`traceparent` headers severs traces. - **Sampling flag propagation**: if the flag says 'not sampled', downstream services honor it and record nothing — consistent, but surprising if you expected data.

  • During a Boot 2 -> Boot 3 migration, why might traces suddenly fragment across services?
    Sleuth defaulted to B3 propagation while Micrometer Tracing in Boot 3 defaults to W3C. A mixed fleet where some services emit W3C and others only read B3 will fail to link spans. The fix is to configure `consume` to accept both formats until every service is upgraded.
  • What exactly is baggage and what does it cost?
    Baggage is user-defined key-value context (e.g. tenantId) propagated alongside the trace/span IDs across every hop. It is useful for cross-cutting context but is copied onto every outbound request, so large baggage adds network and header overhead and can be a data-leak risk if it crosses trust boundaries.

saying these in an interview costs you the question

  • Believing W3C and B3 are interchangeable on the wire without configuration — a mismatch silently breaks traces.
  • Thinking only the trace ID propagates (the parent span ID and sampling flag must too).
  • Assuming context propagates through non-instrumented clients like raw HttpURLConnection.
  • Not knowing the Boot 3 default flipped to W3C from Sleuth's B3.

context