OpenTelemetry Tracing on the JVM


You may know I'm a big fan of OpenTelemetry. I recently finished developing a master class for the YOW! conference at the end of the year. During development, I noticed massive differences in configuration and results across programming languages. Even worse, differences exist across frameworks inside the same programming language.
In this post, I want to compare the different zero-code OpenTelemetry approaches on the JVM, covering the most widespread:
Spring Boot with Micrometer Tracing
Spring Boot with the OpenTelemetry Agent
OpenTelemetry Spring Boot Starter
Quarkus
Quarkus with the OpenTelemetry Agent
Commonalities
I keep the architecture pretty simple:
I'm using Reactive data access on both the remote service and the database to spice up things a bit, more specifically, Kotlin coroutines. Here's the general structure:
val products = coroutineScope {
val ping = async {
// Call the ping service
}
val products = async {
// Query the database
}
println("Received ping response: ${ping.await()}")
products.await()
}
Here are the features for each stack:
Quarkus | Spring Boot | |
Web | Mutiny | WebFlux |
HTTP client | REST client | WebClient |
Database access pattern | Record | Repository |
Database access | Hibernate Reactive with Panache | R2DBC |
Running the OpenTelemetry Agent
The OpenTelemetry Java Agent is the first approach I used regarding OpenTelemetry.
The only necessary configuration is to set the agent when running the JVM:
java -javaagent:opentelemetry-javaagent.jar -jar otel-boot-agent.jar
The agent supports lots of frameworks and libraries, including Spring Boot, Quarkus, Ktor, Spark, and many others. When the application flow finds a supported framework/library, it logs a span.
The Agent upholds the standard OpenTelemetry environment variables.
services:
otel-boot-agent:
build: otel-boot-agent
environment:
OTEL_SERVICE_NAME: OTEL Boot Agent #1
OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318 #2
OTEL_METRICS_EXPORTER: none #3
OTEL_LOGS_EXPORTER: none #3
OpenTelemetry service name
OpenTelemetry endpoint; Spring Boot uses HTTP
Neither metrics nor logs
Here's the Jaeger trace when calling the endpoint on the Spring Boot application:
And here's the one on Quarkus:
Spring Boot features an additional span that displays the repository call. There's no such thing available on Quarkus, as I'm using the Record pattern to access the data.
The Agent outputs the SQL query in both frameworks, i.e., SELECT product.* FROM product
. The Java Agent works out-of-the-box.
Micrometer Tracing on Spring Boot
Spring Boot provides dedicated OpenTelemetry support via Micrometer Tracing.
Micrometer Tracing provides a simple facade for the most popular tracer libraries, letting you instrument your JVM-based application code without vendor lock-in. It is designed to add little to no overhead to your tracing collection activity while maximizing the portability of your tracing effort.
Besides the Micrometer Tracing dependency itself, you need additional ones:
The Spring Boot Actuator
A bridge–OpenTelemetry
An exporter–OpenTelemetry as well
Configuration doesn't follow the OpenTelemetry standard:
services:
otel-boot-micrometer:
environment:
SPRING_APPLICATION_NAME: OTEL Micrometer #1-2
MANAGEMENT_OTLP_TRACING_ENDPOINT: http://jaeger:4318/v1/traces #1-3
Different values from the OpenTelemetry specification
The Spring application name serves as the OpenTelemetry service name
Full path to the API endpoint
The above setup doesn't register the database call. To fix it, we need an additional dependency: R2DBC Proxy.
The new trace contains the database span:
You might notice another issue: calls to the service and the database are sequential, where they should be parallel. It stems from Spring Boot not handling context propagation properly to the coroutine scope. It's an underlying work from the Spring team. Subscribe to the GitHub issue if you're interested.
OpenTelemetry Spring Boot Starter
The OpenTelemetry project provides a Spring Boot starter. You need only a single dependency, and like other starters, Spring Boot magic takes care of configuration:
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
</dependency>
The result is very similar to the previous one, including the not-parallel-but-serial issue.
Quarkus
We saw the results of using the OpenTelemetry Agent in the first section. It's quite straightforward to use OpenTelemetry without the Agent; you need a single dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
Quarkus prefixes regular OpenTelemetry environment variable names with QUARKUS_
:
services:
otel-quarkus:
environment:
QUARKUS_OTEL_SERVICE_NAME: OTEL Quarkus #1
QUARKUS_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: http://jaeger:4317 #2
OpenTelemetry service name
OpenTelemetry endpoint; Quarkus uses gRPC
Results are as expected:
Discussion
OpenTelemetry approaches vary widely in both configuration and results. Unless you're prevented from using Java agents for technical or organizational reasons, I recommend using the OpenTelemetry Agent first. It handles everything you can throw at it out of the box, including the most common libraries. Barring that, you need deep knowledge of the stack you're using, lest results don't represent what happens in reality.
The complete source code for this post can be found on GitHub:
To go further:
Originally published at A Java Geek on August 3rd, 2025
Subscribe to my newsletter
Read articles from Nicolas Fränkel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Nicolas Fränkel
Nicolas Fränkel
Technologist focusing on cloud-native technologies, DevOps, CI/CD pipelines, and system observability. His focus revolves around creating technical content, delivering talks, and engaging with developer communities to promote the adoption of modern software practices. With a strong background in software, he has worked extensively with the JVM, applying his expertise across various industries. In addition to his technical work, he is the author of several books and regularly shares insights through his blog and open-source contributions.