A step-by-step guide to instrumenting Java services with the OpenTelemetry Agent, exporting traces, metrics, and logs to jjhub OBSERVE, and correlating them via trace_id — full observability in about ten minutes.
You need Java 8 or newer (11/17 recommended), a running Java service, and the OBSERVE endpoint and token. This guide uses Spring Boot; other Java frameworks follow the same steps. Start by creating a Java application in the OBSERVE console's "Onboarding" wizard and note the endpoint and token — you'll paste both into the startup flags below.
OpenTelemetry ships a Java Agent that is injected via the -javaagent flag, so you don't touch any business code. The agent uses bytecode instrumentation to wrap your HTTP clients, JDBC calls, message queues, and common frameworks automatically. Download the agent jar, then add these flags to your startup command:
java -javaagent:/opt/otel/opentelemetry-javaagent.jar -Dotel.service.name=order-service -Dotel.traces.exporter=otlp -Dotel.metrics.exporter=otlp -Dotel.logs.exporter=otlp -Dotel.exporter.otlp.endpoint=http://ob.jjhub.cn:4317 -Dotel.exporter.otlp.headers=Authorization=Bearer <token> -jar order-service.jar
service.name is the name that will show up in the OBSERVE topology and dashboards, so pick something stable and meaningful. If the service runs in Docker, either bake the agent jar into the image or mount it as a volume, then pass the same parameters via environment variables — the agent reads both system properties and env vars, which is convenient for containerized deployments where you can't easily edit the startup command.
The result is a fully instrumented service with zero source changes: incoming HTTP requests produce spans, outgoing calls produce child spans, and the whole thing forms a trace. For services that run in multiple environments (staging vs production) or multiple replicas of the same logical service, also set -Dotel.resource.attributes=deployment.environment=prod so OBSERVE can separate them; without it, staging and production traces get mixed together and every investigation becomes noisier than it needs to be.
Beyond traces, the agent exports metrics with no extra configuration: JVM memory and GC, HTTP request rates, and latency histograms all appear under the service name you set in service.name. If you want metrics but not traces (or the reverse), disable exporters independently — remove -Dotel.traces.exporter=otlp to keep traces off while metrics keep flowing. After startup, wait a minute and check the OBSERVE console for the service; if nothing shows up, the usual culprits are the endpoint/port, the token, or an outbound network rule.
Traces alone aren't enough — the real payoff is jumping from a trace straight to its logs. Do that by injecting trace_id and span_id into your log output. For Spring Boot with Logback, add the MDC fields to the pattern in logback-spring.xml:
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}
[trace_id=%X{trace_id},span_id=%X{span_id}] - %msg%n</pattern>
The agent writes trace_id and span_id into the MDC automatically, so every log line printed during a request carries them. Once logs are shipped to OBSERVE via OTLP or file collection, open any trace in the console and the correlated logs appear on the right side. From a slow request you can see every log line it emitted, in order, without grepping across hosts.
No traces visible: confirm the exporter is otlp and the port is correct — 4317 is gRPC, 4318 is HTTP, and the agent defaults to gRPC. Also check that the service has outbound network access to the endpoint, and that the token is valid. A quick way to isolate the problem is to look at the agent's own debug output by adding -Dotel.javaagent.debug=true.
Logs missing trace_id: verify the MDC fields are in the pattern and that logs pass through the agent's log bridge. If you use a custom logging setup rather than Logback's defaults, make sure the bridge is in place. Legacy projects on log4j1 need extra configuration.
Sampling: high-traffic services should set -Dotel.traces.sampler=parentbased_traceidratio with -Dotel.traces.sampler.arg=0.1 to keep 10% of traces and control cost; raise it temporarily when investigating a specific incident so you don't miss the requests you care about. If spans start dropping under load, the first knob to try is the exporter queue: -Dotel.bsp.schedule.delay controls how often spans flush, and raising -Dotel.bsp.max.queue.size buffers bursts at the cost of memory. Default batching handles most services, but chatty ones benefit from a larger queue.
After onboarding, open the "Application Topology" view to see call relationships between services; click into any trace and traces, metrics, and logs are already linked together.