Unravel › Java concepts › Cloud and operations
Distributed tracing (OpenTelemetry)
A single user request can touch ten services.
Learn these first
At a glance
A parcel tracking number.
- 1A trace is one end-to-end request; a span is one unit of work inside it (an HTTP handler, a database call)Spans have a parent, attributes, events and a status.
- 2tracer.spanBuilder("charge-card").startSpan() starts one; try (Scope s = span.makeCurrent()) { ..} finally { span.end(); } makes it current so child spans attach to it. Annotations (@WithSpan, @Observed) do this declaratively.
- 3Context travels between services as headers (traceparent of W3C Trace Context), written by a client interceptor and read by the server, so the child span joins the same traceMessaging libraries carry it in message headers.
- 4Spans are exported (OTLP) to a backend (Jaeger, Tempo, Datadog, X-Ray)Spring Boot 3 wires Micrometer Tracing to OpenTelemetry or Brave automatically.
- Always end spans, in a finally block; a leaked span never reaches the backend and corrupts the parent timing
- Sample, do not trace everything at full volume
- The current span is stored in a context object held in a thread-local (or in Reactor context for reactive code), which is why context is lost when work moves to another thread unless a wrapped executor propagates it
Span span = tracer.spanBuilder("charge-card").startSpan();try (Scope scope = span.makeCurrent()) { span.setAttribute("order.id", orderId); paymentClient.charge(orderId, amount);} catch (Exception e) { span.recordException(e); span.setStatus(StatusCode.ERROR); throw e;} finally { span.end();}- line 1 uses spanBuilder, the core of Distributed tracing (OpenTelemetry)
- line 2 uses makeCurrent, the core of Distributed tracing (OpenTelemetry)
- line 7 uses StatusCode, the core of Distributed tracing (OpenTelemetry)
Read the full explanation (about 2 min)
A single user request can touch ten services. Distributed tracing gives the request an id, records each step as a span with start time and duration, and joins them into one timeline so you can see where the time went and what failed.
A mental model
A parcel tracking number. Every depot that handles the parcel scans it, and the tracking page shows the full journey and where it sat for three days.
How it works
A trace is one end-to-end request; a span is one unit of work inside it (an HTTP handler, a database call). Spans have a parent, attributes, events and a status.
tracer.spanBuilder("charge-card").startSpan() starts one; try (Scope s = span.makeCurrent()) { ... } finally { span.end(); } makes it current so child spans attach to it. Annotations (@WithSpan, @Observed) do this declaratively.
Context travels between services as headers (traceparent of W3C Trace Context), written by a client interceptor and read by the server, so the child span joins the same trace. Messaging libraries carry it in message headers.
Spans are exported (OTLP) to a backend (Jaeger, Tempo, Datadog, X-Ray). Spring Boot 3 wires Micrometer Tracing to OpenTelemetry or Brave automatically.
Going deeper
Always end spans, in a finally block; a leaked span never reaches the backend and corrupts the parent timing. Record exceptions with span.recordException(e) and set an error status.
Sample, do not trace everything at full volume. Head sampling (decide at the start, for example 10%) is cheap; tail sampling (keep slow and failed traces) needs a collector but gives more useful data.
Do not put personal data or full payloads in span attributes. Traces are widely readable and retained.
Under the hood
The current span is stored in a context object held in a thread-local (or in Reactor context for reactive code), which is why context is lost when work moves to another thread unless a wrapped executor propagates it. Exporters batch finished spans and send them from a background thread, so the cost on the request path is small.
Common mistakes
- Spans that are never ended.
- Losing the context at thread hand-offs.
- Putting personal data in attributes.
- Tracing 100% of traffic without a sampling plan.
How Unravel recognises it
When you paste code, Unravel looks for these signs of distributed tracing (opentelemetry). Spotting them yourself is a good habit when you read unfamiliar code:
- Annotations:
@Observed@WithSpan@NewSpan@ContinueSpan - Types:
OpenTelemetryGlobalOpenTelemetryTracerSpanBuilderSpanContextSpanKindStatusCodeTextMapPropagatorBaggageObservationRegistryTraceContextSdkTracerProviderOtlpGrpcSpanExporterSpanExporterand 2 more - Method calls:
startSpan()spanBuilder()makeCurrent()
Part of a learning path
- Cloud-native Java · previous: Serverless function handlers (AWS Lambda, Azure, GCP) · next: Structured logging and correlation ids (MDC)
Related concepts
Frequently asked questions
What is Distributed tracing (OpenTelemetry)?
A single user request can touch ten services. Distributed tracing gives the request an id, records each step as a span with start time and duration, and joins them into one timeline so you can see where the time went and what failed.
What are the most common mistakes with Distributed tracing (OpenTelemetry)?
Spans that are never ended. Losing the context at thread hand-offs. Putting personal data in attributes. Tracing 100% of traffic without a sampling plan.
How can I see Distributed tracing (OpenTelemetry) in real code?
Paste your own Java into Unravel and it will point out where distributed tracing (opentelemetry) appears, explain how it works at the detail level you choose, and draw the flow of the code around it.