Wire Spring Boot tracing into Observe with the OpenTelemetry Java Agent — no code changes. Covers startup flags, sampling, log correlation and the pitfalls that actually bite.
The most time-consuming part of debugging a slow endpoint is figuring out which services a request actually touched and where it stalled. Logs only give you a single service's perspective: each service logs its own lines, and you end up grepping five repos to reconstruct a path the request already took. Tracing stitches one request's path across many services into a single chain, with each span recording one segment of time. Once tracing is in place, a three-second order endpoint becomes easy to localize: was it the database, the cache, or a downstream RPC? You can see it at a glance instead of bisecting the stack by hand.
This guide wires a Spring Boot 3 application into Observe using the OpenTelemetry Java Agent. No changes to business code, no hand-written spans. If you're on Spring Boot 2.x, the same steps apply — the agent instruments both.
OpenTelemetry ships an official Java agent that auto-instruments Spring MVC, RestTemplate, Redis, MySQL and dozens of other common libraries when attached at JVM startup. You download one jar and attach it at launch — that's the entire instrumentation story. Pin a specific version rather than always fetching "latest", so a silent agent upgrade doesn't change your tracing behavior in the middle of a release.
curl -LO https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
Point the agent's OTLP exporter at Observe's ingestion gateway with startup flags:
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.traces.exporter=otlp \
-Dotel.exporter.otlp.endpoint=http://ob.example.com:4317 \
-Dotel.service.name=order-service \
-Dotel.metrics.exporter=none \
-jar app.jar
If you run inside Tomcat, put the same flags into JAVA_OPTS. Name the service after the deployment unit — don't leave the default unknown_service, or every service will collapse into one blob in the UI and the traces become useless. Note the metrics.exporter=none line: we're shipping traces first. Turning metrics on at the same time doubles the moving parts; you can enable it later once traces are stable.
Cross-service propagation uses the W3C Trace Context standard and is on by default; HTTP calls automatically carry the traceparent header from one service to the next. Sampling decides how many traces you store. Start with parentbased_always_on so every entry request produces a trace, then switch to a ratio-based sampler as volume grows.
-Dotel.traces.sampler=parentbased_traceidratio
-Dotel.traces.sampler.arg=0.1
While you're at it, put the trace id into your logs so you can jump from a single error line to the whole trace. The agent writes the trace id into the MDC; surface it in the logback pattern:
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%X{trace_id}] %msg%n</pattern>
That one line turns "I found the error" into "I found the error and the entire request that caused it".
Send one real request, then open the Tracing page and filter by trace id or by service plus time. You should see a full waterfall, every span annotated with its duration. Click the slow span to see the SQL or downstream call it made — you're basically at the root cause already. A trace that shows five spans with a single 2.9-second span at the bottom is a far better starting point than a wall of logs.
On overhead: the Java agent typically adds low single-digit percentage CPU and a modest amount of memory. If you're concerned, turn on sampling (as above) and benchmark a staging instance before rolling out to production.
Common pitfalls: match the agent version to your JDK (JDK 21 needs a recent agent); open port 4317 in the firewall; and if you only see a single flat span with no children, your HTTP client probably isn't using an auto-instrumented library — check which client you're actually calling downstream services with, and swap in one that's on the agent's list.
That's the whole loop: download, configure, verify. Once traces are flowing, the next step is usually to add span attributes that carry your business context — order id, user id, tenant — so the waterfall reads like your own domain rather than a generic HTTP trace. The agent can attach these via environment variables or a small ResourceProvider, still without touching business logic. From there, the payoff compounds: the same trace data feeds latency dashboards, error-rate alerts and the trace id in your logs, so one request can be followed end to end instead of being reassembled by hand.