skip to content

What is the role of the Brave vs OpenTelemetry bridge in Micrometer Tracing, and what dependencies do you need to export spans to Zipkin?

level: seniorimportance: should knowfreq 50%

answer

  1. Micrometer Tracing = facade, bridge = real tracer
  2. bridge-brave OR bridge-otel — never both
  3. Brave -> zipkin-reporter-brave; OTel -> opentelemetry-exporter-zipkin
  4. endpoint default :9411/api/v2/spans
  5. sampling.probability default 0.1 gates export

basics

~20 s

Micrometer Tracing defines a vendor-neutral API; a bridge plugs in a real tracer — either Brave or OpenTelemetry. To send spans to Zipkin you add the bridge plus a Zipkin reporter/exporter dependency; Spring Boot auto-configures the endpoint.

solid answer

~40 s

Micrometer Tracing is a **facade**: it exposes a neutral `Tracer`/propagation API but does no tracing itself. You must add exactly one **bridge** that supplies the actual implementation: `micrometer-tracing-bridge-brave` (Brave, the Zipkin-native tracer) or `micrometer-tracing-bridge-otel` (OpenTelemetry SDK). To ship spans to **Zipkin**, add a reporter matching your bridge: with Brave use `zipkin-reporter-brave`; with the OTel bridge use `opentelemetry-exporter-zipkin`. Spring Boot's `ZipkinAutoConfiguration` then wires a sender pointing at `management.zipkin.tracing.endpoint` (default `http://localhost:9411/api/v2/spans`). Choose **Brave** if you're Zipkin-centric and want the lightest path; choose **OTel** if you want the OpenTelemetry ecosystem, OTLP export, and to send to Tempo/Jaeger/collectors. You never put both bridges on the classpath — they conflict.

code

groovy · 23 lines
groovy
// --- Option A: Brave bridge -> Zipkin (lightweight, Zipkin-centric) ---
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'io.micrometer:micrometer-tracing-bridge-brave'
    implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
}

// --- Option B: OpenTelemetry bridge -> Zipkin (OTel ecosystem) ---
// dependencies {
//     implementation 'org.springframework.boot:spring-boot-starter-actuator'
//     implementation 'io.micrometer:micrometer-tracing-bridge-otel'
//     implementation 'io.opentelemetry:opentelemetry-exporter-zipkin'
// }
// NOTE: never combine bridge-brave and bridge-otel in the same app.

// application.yml
// management:
//   tracing:
//     sampling:
//       probability: 1.0   # dev: export everything (default is 0.1)
//   zipkin:
//     tracing:
//       endpoint: http://localhost:9411/api/v2/spans

go deeper

for a junior

Know you need actuator + a bridge + a Zipkin reporter.

for a middle

Explain the facade/bridge split and the default endpoint and sampling probability.

for a senior

Justify Brave vs OTel per use case and debug an empty-Zipkin situation systematically.

for a principal

Standardize the tracing stack across services, weigh OTel Collector/OTLP vs direct Zipkin, and manage migration/cost.

## The facade + bridge model Micrometer Tracing does not implement spans or propagation. It defines abstractions — `io.micrometer.tracing.Tracer`, `Span`, `Propagator`, `BaggageManager` — and delegates to a real tracing library through a **bridge**. This is the same 'pick your implementation' pattern as SLF4J. You must include **exactly one** bridge. ## The two bridges 1. **Brave bridge** — `io.micrometer:micrometer-tracing-bridge-brave`. Brave is the tracer originally built for Zipkin/OpenZipkin. It is lightweight and the most direct route if your backend is Zipkin. Propagation uses Brave's `Propagation` (B3 native, W3C supported). 2. **OpenTelemetry bridge** — `io.micrometer:micrometer-tracing-bridge-otel`. This delegates to the **OpenTelemetry SDK**. Choose it to join the broader OTel ecosystem: OTLP protocol, the OpenTelemetry Collector, auto-instrumentation agents, and backends like Grafana Tempo or Jaeger. Propagation uses OTel's `TextMapPropagator`. **Never add both** — you get two `Tracer` beans / conflicting auto-configuration. Pick one per application. ## Exporting to Zipkin The bridge produces spans in memory; an **exporter/reporter** serializes and sends them. Match the reporter to the bridge: - **Brave -> Zipkin**: add `io.zipkin.reporter2:zipkin-reporter-brave`. It uses the Zipkin v2 JSON format over HTTP. - **OTel -> Zipkin**: add `io.opentelemetry:opentelemetry-exporter-zipkin`. Spring Boot's **`ZipkinAutoConfiguration`** detects the reporter and configures the sender from: - `management.zipkin.tracing.endpoint` — default `http://localhost:9411/api/v2/spans`. - Related properties for connect/read timeouts and encoding. ## Sampling gates export Export is downstream of the **sampling decision**. `management.tracing.sampling.probability` (default **0.1** = 10%) controls what fraction of traces are recorded and thus exported. In dev you typically set it to `1.0`. A common 'nothing shows up in Zipkin' cause is leaving the default 0.1 (or 0) and only sending a few requests. ## Choosing a bridge — decision factors - **Zipkin-only, minimal footprint, existing Brave/Sleuth-B3 fleet** -> Brave bridge + zipkin-reporter-brave. - **Multi-backend / future-proofing / OTel Collector / OTLP / Tempo / Jaeger** -> OTel bridge + the matching exporter (`opentelemetry-exporter-otlp` for OTLP, `opentelemetry-exporter-zipkin` for Zipkin). - **Agent-based auto-instrumentation** (OpenTelemetry Java agent) generally pairs with the OTel path. ## What Spring Boot auto-configures With actuator + a bridge + a reporter on the classpath, Boot wires: the `Tracer`, the `ObservationHandler`s that translate observations to spans, the propagators, and the Zipkin sender. You usually only set the endpoint and sampling probability. ## Gotchas - **Both bridges present** -> startup/bean conflicts. Enforce one via dependency management. - **Bridge without reporter** -> spans are created and appear in logs (trace IDs) but nothing reaches Zipkin. - **Reporter mismatched to bridge** (e.g. otel exporter with brave bridge) -> not wired. - **Endpoint wrong / Zipkin down** -> spans dropped; the reporter typically logs and moves on rather than failing requests. - **Sampling 0.1 default** -> most traces silently not exported; looks like it's 'not working'. - **OTel bridge != OTLP automatically**: the OTel bridge still needs an exporter dependency for whatever protocol/backend you target.

  • You added `micrometer-tracing-bridge-brave` and see trace IDs in your logs, but Zipkin is empty. What are the likely causes?
    Most likely: (1) no exporter dependency (`zipkin-reporter-brave`) so spans are created but never sent; (2) sampling probability at the default 0.1 or 0, so few/no traces are recorded; (3) wrong or unreachable Zipkin endpoint. Trace IDs in logs only prove the bridge works, not that export is wired.
  • When would you pick the OpenTelemetry bridge over Brave?
    When you want the OpenTelemetry ecosystem: OTLP export to an OTel Collector, backends like Grafana Tempo or Jaeger, alignment with OTel auto-instrumentation agents, or vendor-neutral portability. Brave is preferable when you're Zipkin-only and want the lightest, most direct setup.

saying these in an interview costs you the question

  • Putting both the Brave and OTel bridges on the classpath.
  • Expecting spans in Zipkin with a bridge but no exporter/reporter dependency.
  • Forgetting that the default sampling probability is 0.1, so most traces are never exported.
  • Pairing an OTel exporter with the Brave bridge (or vice versa).

context