← Back to blog
Guide 4 min read 炬鲸团队

Instrument Spring Boot Tracing with OpenTelemetry: From Zero to a Full Waterfall

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.

First, be clear about what tracing solves

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.

Step 1: Get the agent

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

Step 2: Configure the exporter

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.

Step 3: Propagation, sampling and log correlation

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".

Step 4: Verify with a real request

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.