A step-by-step guide to auto-instrumenting a Spring Boot service with the OpenTelemetry Java Agent, covering agent setup, JVM flags, sampling, log correlation, and common pitfalls.
Before you start, make sure you have three things: a JDK 8 or newer, a working Spring Boot service, and an OTLP endpoint plus token from the console's "Onboarding" page. The examples below use https://ob.jjhub.cn/otlp. The host running the service must be able to reach that endpoint — allow port 4317 for gRPC or 4318 for HTTP in the firewall. This reachability check saves more setup time than almost anything else, because a blocked port is the single most common reason "it just doesn't show up".
Download the javaagent jar from the official OpenTelemetry releases and place it on the host, for example /opt/otel/opentelemetry-javaagent.jar. The agent works by attaching to the JVM at startup and instrumenting common libraries automatically — Spring MVC, HTTP clients, JDBC, message queues, and more — so you get traces without touching application code. Match the agent version to your JDK: use the 1.x line on JDK 8, and 2.x on JDK 17 and above. A version mismatch is the most common cause of silent failure, so verify this before you move on, and optionally check the published checksum to confirm the download is intact.
Add -javaagent to the JVM startup and set these environment variables:
OTEL_SERVICE_NAME=order-service
OTEL_EXPORTER_OTLP_ENDPOINT=https://ob.jjhub.cn/otlp
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer <token>
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
java -javaagent:/opt/otel/opentelemetry-javaagent.jar -jar order-service.jar
OTEL_SERVICE_NAME is the identity under which all your telemetry is grouped, so name it "environment-service" — for example prod-order-service — so data from different environments never mixes. In a container environment, mount these variables directly into the Deployment's env section and bake the agent jar into the image, so every replica gets the same configuration automatically.
Full 100% sampling is expensive at scale. Use ratio-based sampling in production:
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1
parentbased_traceidratio keeps child spans consistent with the parent's decision, so you never end up with only half of a call chain sampled — a classic problem with naive ratio samplers. You can also append custom attributes such as environment and version with OTEL_RESOURCE_ATTRIBUTES, which makes it easier to filter by dimension and compare canary versions on the platform.
Fire a real request with curl, and traces should appear in the trace view within 30 seconds. If nothing shows up, work through the three most common causes in order:
-Dotel.javaagent.debug=true to see the startup logs and confirm the agent actually loaded.To correlate logs with traces automatically, add the OpenTelemetry logback/log4j bridge so trace_id and span_id are injected into the log context.
Out of the box, the agent gives you request traces across your HTTP and database calls, JVM-level metrics like heap and GC, and log correlation with the bridge. That is usually enough to see the shape of a request and spot slow spans. When you need finer detail — a business identifier on every span, or custom spans around a critical method — add manual instrumentation with the OpenTelemetry SDK and @WithSpan, or annotate your spans with order IDs and user IDs so you can search traces by business key. Once everything is verified, bake the agent flags into the image or startup script so a future release never ships without instrumentation. Ten minutes the first time, zero minutes every time after. Two settings are worth deciding on day one rather than later: only export the signal types you actually consume, because turning on metrics and logs alongside traces roughly triples data volume, and validate in staging with the same environment variables before production so a misconfigured exporter never reaches customers.
A five-minute staging check is worth the time: fire a known request, then confirm its trace_id appears both in the trace view and in the correlated log lines. If the trace shows up but the logs don't carry the trace_id, the bridge isn't wired correctly — a very common half-finished state that is far cheaper to catch before a 2 a.m. incident.