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:

Architecture overview

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:

QuarkusSpring Boot
WebMutinyWebFlux
HTTP clientREST clientWebClient
Database access patternRecordRepository
Database accessHibernate Reactive with PanacheR2DBC

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
  1. OpenTelemetry service name

  2. OpenTelemetry endpoint; Spring Boot uses HTTP

  3. Neither metrics nor logs

Here's the Jaeger trace when calling the endpoint on the Spring Boot application:

Trace of Spring Boot with OpenTelemetry Agent

And here's the one on Quarkus:

Trace of Quarkus with OpenTelemetry Agent

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
  1. Different values from the OpenTelemetry specification

  2. The Spring application name serves as the OpenTelemetry service name

  3. 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:

Trace of Spring Boot with Micrometer and R2DBC Proxy

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>

Trace of Spring Boot with the OpenTelemetry Starter

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
  1. OpenTelemetry service name

  2. OpenTelemetry endpoint; Quarkus uses gRPC

Results are as expected:

Trace of Quarkus

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

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