Instrumenting Java and Spring Boot with OpenTelemetry means attaching the OpenTelemetry Java agent, or using the Spring Boot starter, to emit traces, metrics and logs over OTLP without changing application code..
Instrumenting Java and Spring Boot with OpenTelemetry means attaching the OpenTelemetry Java agent, or using the Spring Boot starter, to emit traces, metrics and logs over OTLP without changing application code.
A Java service adds a layer no interpreted runtime has: the JVM. So beyond per-endpoint latency and error rate and JDBC query timing, you watch JVM health, heap usage by pool, garbage-collection pause time and frequency, and thread counts, because the most confusing Java latency, requests that pause for no application reason, is almost always a garbage-collection pause or heap pressure, not the code.
The combination that pinpoints most incidents is an endpoint latency histogram alongside GC pause time and heap-after-GC on the same timeline: when latency spikes line up with GC pauses, you are looking at a memory problem, not a logic one.
Java has the most mature zero-code path in OpenTelemetry: the Java agent attaches with -javaagent at JVM start and auto-instruments a large set of libraries, the Servlet stack and Spring MVC, JDBC, popular HTTP and messaging clients, so requests and downstream calls are traced without touching the code. It also emits JVM runtime metrics, heap by pool, GC, threads, class loading, from the same agent.
For Spring Boot specifically there is a second option: the OpenTelemetry Spring Boot starter, which integrates with the framework and with Micrometer, the metrics facade Spring Boot Actuator already uses. Both export over OTLP. The practical caution is to pick one path for a given signal so you do not emit JVM metrics twice from both the agent and Micrometer; agent for traces plus one consistent source for metrics is the clean setup.
The signals that explain most JVM service incidents:
| Signal | Source | What it tells you / watch for |
|---|---|---|
http.server.request.duration | Servlet/Spring instrumentation | Per-endpoint latency histogram; track p95/p99 by route. |
jvm.memory.used (by pool) | JVM runtime | Old-generation heap that stays high after GC is the signature of a leak. |
jvm.gc.duration | JVM runtime | Long or frequent pauses freeze request threads and appear as latency spikes. |
jvm.thread.count | JVM runtime | Climbing thread count can mean a thread pool is not releasing, heading toward exhaustion. |
db.client.operation.duration | JDBC instrumentation | Query latency; if it dominates a request the fix is in the query or connection pool. |
connection pool active/idle | pool instrumentation | A pool at its max with requests waiting is a common, silent source of latency. |
A Spring Boot dashboard should make a GC problem instantly distinguishable from a code or dependency problem. Row one: endpoint p95/p99 and 5xx by route. Row two, the JVM: heap used by pool (watch old-gen after GC), GC pause time, and thread count. Row three: JDBC query latency and connection-pool utilization, since pool exhaustion is a frequent cause of latency that looks like a slow database.
# p99 latency by endpoint
histogram_quantile(0.99, http.server.request.duration) by (http.route)
# GC pause time (spikes here line up with latency spikes)
rate(jvm.gc.duration[5m])
# old-gen heap after GC: a rising floor = leak
jvm.memory.used{pool="G1 Old Gen"}
Requests pause in lockstep with garbage-collection events while the application code is idle. Overlay jvm.gc.duration on the latency histogram to confirm. Causes are an undersized heap, high allocation rate, or a leak filling old-gen; tune heap and allocation, or fix the leak.
Old-generation heap-after-GC creeps up over hours or days and never returns to its baseline, ending in an OutOfMemoryError. The trend on jvm.memory.used for the old-gen pool is the tell. Common causes are static collections and cache entries that are never evicted.
Latency climbs and requests time out while CPU and heap look fine, because threads or JDBC connections are all checked out and new requests wait. Watch thread count and pool active-vs-max; the fix is sizing the pool or finding the leak that holds connections open.
Starting thresholds to tune:
| Condition | Signal | Meaning |
|---|---|---|
| 5xx rate by route > 2% for 5m | errors | Endpoint failing. |
| jvm.gc.duration rate > 10% of wall time | GC | GC is eating a tenth of your runtime; latency will suffer. |
| old-gen heap after GC trending up over 24h | memory | Probable leak; schedule investigation before OOM. |
| connection pool active = max for 5m | database | Pool exhausted; requests are waiting on connections. |
Ops Singularity's TelemetryOps ingests Java and Spring Boot OpenTelemetry data natively, and Sentinel AI resolves the incidents it reveals, including JVM-level issues, through governed Action Tickets. Explore TelemetryOps.
Attach the OpenTelemetry Java agent with -javaagent and set the OTLP endpoint, or add the OpenTelemetry Spring Boot starter for framework-native integration with Micrometer metrics.
No. The Java agent attaches at JVM startup and auto-instruments a wide range of libraries, so you get traces and metrics without changing application code.
Bring a real incident. We will show you Sentinel investigate, act and verify end to end, with every action reversible and audited.