OpenTelemetry Tracing Bridge (juneau-rest-server-tracing-otel)
The juneau-rest-server-tracing-otel module bridges every @RestOp call to an OpenTelemetry
Tracer, opening a SERVER span per
request, propagating incoming W3C trace context, and injecting the resulting traceparent header
on the response.
This page covers the module-specific classes and wiring patterns. The shared opt-in contract, the
TracerHook SPI, per-resource / per-operation annotation overrides, log correlation, and the
custom (non-request) observation API are described in
Observability — Micrometer + OpenTelemetry.
Maven dependency
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-rest-server-tracing-otel</artifactId>
<version>${juneau.version}</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>1.43.0</version> <!-- consumer-supplied; provided scope on the bridge module -->
</dependency>
<!-- typically also the SDK + an exporter of your choice -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<version>1.43.0</version>
</dependency>
opentelemetry-api is declared in provided scope on the bridge module. Consumers pick their
own OTel version (and SDK / exporter); core juneau-rest-server stays OTel-free.
Classes in this module
| Class | Role |
|---|---|
OtelTracerHook | TracerHook impl that opens OTel SERVER spans per request. |
RestRequestTextMapGetter | TextMapGetter<RestRequest> used to extract W3C propagation headers. |
OtelTracerHook
OtelTracerHook is the TracerHook bridge. Register one as a @Bean on your resource (or in
a Spring @Configuration) to activate distributed tracing:
Constructors
// Option A — use GlobalOpenTelemetry (recommended with the OTel auto-instrumentation agent).
new OtelTracerHook()
// Option B — explicit OpenTelemetry instance (e.g. multi-tenant, or manual SDK assembly).
new OtelTracerHook(OpenTelemetry otel)
// Option C — explicit Tracer + TextMapPropagator (typically for unit tests).
new OtelTracerHook(Tracer tracer, TextMapPropagator propagator)
The three-arg constructor accepts any Tracer / TextMapPropagator combination, which is
especially useful in tests backed by OTel's in-memory SDK (opentelemetry-sdk-testing).
The instrumentation scope name used when obtaining a Tracer from an OpenTelemetry instance
is exposed as the constant OtelTracerHook.DEFAULT_INSTRUMENTATION_NAME
("org.apache.juneau.rest.server").
Wiring
@Configuration
public class ObservabilityConfig {
// Option A: rely on the JVM-wide GlobalOpenTelemetry (configured by the otel-java-agent or
// by calling GlobalOpenTelemetry.set(...) at startup).
@Bean
public TracerHook tracerHook() {
return new OtelTracerHook();
}
// Option B: explicit SDK instance (e.g. for tests or multi-tenant setups).
@Bean
public TracerHook tracerHook(OpenTelemetry otel) {
return new OtelTracerHook(otel);
}
}
Span shape
Each @RestOp invocation produces one span with the following attributes:
| Attribute / property | Value |
|---|---|
| Span kind | SpanKind.SERVER |
| Span name | Uppercased HTTP method (GET, POST, …) per the OTel HTTP semantic conventions. |
http.request.method | Uppercased HTTP method. |
http.response.status_code | HTTP response status (set immediately before the span closes, so error paths receive the framework's resolved status). |
http.route | The @RestOp path template (e.g. /users/{id}), not the raw URI — keeps trace cardinality bounded. |
On the exception path:
Span.recordException(throwable)adds an exception event carrying the stack trace.- The span status is set to
StatusCode.ERROR. - An
exception.typeattribute carries the exception's simple-name (e.g.IllegalStateException).
A 2xx status leaves the span status at StatusCode.UNSET per the OTel HTTP semantic conventions
(servers should not pre-color successful spans as OK).
The attribute key constants are exposed on OtelTracerHook for programmatic use:
OtelTracerHook.ATTR_HTTP_REQUEST_METHOD // AttributeKey<String> "http.request.method"
OtelTracerHook.ATTR_HTTP_RESPONSE_STATUS_CODE // AttributeKey<Long> "http.response.status_code"
OtelTracerHook.ATTR_HTTP_ROUTE // AttributeKey<String> "http.route"
OtelTracerHook.ATTR_EXCEPTION_TYPE // AttributeKey<String> "exception.type"
W3C trace-context propagation
Incoming traceparent
OtelTracerHook uses RestRequestTextMapGetter to extract traceparent / tracestate (and any
other propagator-defined headers, e.g. baggage) from the incoming request before starting the
span, so the server span is created as a child of the caller's distributed trace:
Incoming request: traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
Server span: traceId = 0af7651916cd43dd8448eb211c80319c (continued from caller)
parentSpan = b7ad6b7169203331
spanId = <freshly minted>
The configured TextMapPropagator is used for extraction (default:
W3CTraceContextPropagator). The same propagator handles outgoing downstream headers when the
outbound HTTP client instruments the OTel Context.current() (all standard OTel client
instrumentations do).
Outgoing response traceparent
OtelTracerHook stashes the server-started span's W3C trace-context strings as request
attributes at span-start time. A TraceContextResponseProcessor in juneau-rest-server's
response chain reads those attributes and writes them as traceparent (and tracestate when
non-empty) on the HTTP response:
Response: traceparent: 00-0af7651916cd43dd8448eb211c80319c-<server-span-id>-01
This lets a caller correlate its own logs with the server's span without a tracing agent.
The response-header injection is on by default whenever a non-no-op TracerHook is active.
Opt out globally with:
-DRestContext.responseTraceparent=false
RestRequestTextMapGetter
RestRequestTextMapGetter is the TextMapGetter<RestRequest> singleton that OtelTracerHook
uses to read propagation headers from incoming requests. It is exposed as a public singleton
(RestRequestTextMapGetter.INSTANCE) so custom TracerHook implementations built on top of
OtelTracerHook (or custom code that calls the OTel propagator directly) can reuse it:
TextMapGetter<RestRequest> getter = RestRequestTextMapGetter.INSTANCE;
Context extracted = propagator.extract(Context.current(), request, getter);
Header lookups are case-insensitive at the underlying RequestHeaderList, matching the RFC 9110
contract.
Custom (non-request) spans
OtelTracerHook also overrides the non-request startSpan(String spanName) default method,
opening an INTERNAL-kind span parented to the current OTel context. This integrates
transparently with Observations.observe(...) for tracing arbitrary code blocks:
try (Observation o = Observations.observe("order.load", "team=payments")) {
return loadOrder(id);
} catch (RuntimeException e) {
o.setError(e);
throw e;
}
When called from inside an active @RestOp handler, the resulting span is automatically nested
under the active request span.
Full wiring example
@Configuration
public class ObservabilityConfig {
// Wire the OTel SDK (here: using GlobalOpenTelemetry configured by the otel-java-agent).
@Bean
public TracerHook tracerHook() {
return new OtelTracerHook();
}
}
@Rest(
path="/api",
observability="true" // fail-fast if no backend is wired
)
public class ApiResource extends BasicRestServlet {
@RestGet("/users/{id}")
public User getUser(@Path String id) { ... }
@RestGet("/health")
@RestOp(observability="false") // exclude health-check from tracing
public String health() { return "ok"; }
}
With an OTel-agent (or a manually configured SDK) in place, each call to GET /api/users/{id}
produces one SERVER span exported to the configured backend (Jaeger, Zipkin, OTLP collector,
etc.).
One-dependency OTLP export
To wire both metrics and traces over OTLP in one go, use the curated OTLP bundle:
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-observability-otlp-bundle</artifactId>
<version>${juneau.version}</version>
<type>pom</type>
</dependency>
It transitively brings juneau-rest-server-tracing-otel, juneau-rest-server-metrics-micrometer,
opentelemetry-exporter-otlp (+ SDK + autoconfigure), and micrometer-registry-otlp. You still
wire the @Bean TracerHook / @Bean MetricsRecorder explicitly. See
Dependency Management (BOM & Bundles).
See also
- Observability — Micrometer + OpenTelemetry — overview,
the
TracerHookSPI, opt-in contract, outgoing responsetraceparent, log correlation, and custom observations. - Micrometer Metrics Bridge — the companion module for
recording
http.server.requestsmetrics. - OTel HTTP semantic conventions — upstream specification the span shape follows.
- W3C Trace Context — the propagation format used for
traceparent/tracestate.