<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged coroutines</title>
<link>https://coderlegion.com/tag/coroutines</link>
<description>Powered by Question2Answer</description>
<item>
<title>OpenTelemetry Tracing on the JVM</title>
<link>https://coderlegion.com/4498/opentelemetry-tracing-on-the-jvm</link>
<description>&lt;p&gt;You may know I'm a big fan of OpenTelemetry. I recently finished developing a &lt;a rel=&quot;nofollow&quot; href=&quot;https://yowcon.com/melbourne-2025/masterclasses/560/gain-practical-in-depth-experience-with-observability-using-opentelemetry&quot;&gt;master class for the YOW! conference&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;In this post, I want to compare the different zero-code OpenTelemetry approaches on the JVM, covering the most widespread:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Boot with Micrometer Tracing&lt;/li&gt;
&lt;li&gt;Spring Boot with the OpenTelemetry Agent &lt;/li&gt;
&lt;li&gt;OpenTelemetry Spring Boot Starter&lt;/li&gt;
&lt;li&gt;Quarkus&lt;/li&gt;
&lt;li&gt;Quarkus with the OpenTelemetry Agent&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;commonalities&quot;&gt;Commonalities&lt;/h3&gt;
&lt;p&gt;I keep the architecture pretty simple:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11040439081269176861&quot; alt=&quot;Architecture overview&quot;&gt;&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val products = coroutineScope {
    val ping = async {
        // Call the ping service
    }
    val products = async {
        // Query the database
    }
    println(&quot;Received ping response: ${ping.await()}&quot;)
    products.await()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are the features for each stack:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Quarkus &lt;/th&gt;&lt;th&gt;Spring Boot&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Web &lt;/td&gt;&lt;td&gt;Mutiny &lt;/td&gt;&lt;td&gt;WebFlux&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;HTTP client &lt;/td&gt;&lt;td&gt;REST client &lt;/td&gt;&lt;td&gt;&lt;code&gt;WebClient&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Database access pattern &lt;/td&gt;&lt;td&gt;Record &lt;/td&gt;&lt;td&gt;Repository&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Database access &lt;/td&gt;&lt;td&gt;Hibernate Reactive with Panache &lt;/td&gt;&lt;td&gt;R2DBC&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;running-the-opentelemetry-agent&quot;&gt;Running the OpenTelemetry Agent&lt;/h3&gt;
&lt;p&gt;The OpenTelemetry Java Agent is the first approach I used regarding OpenTelemetry.&lt;/p&gt;
&lt;p&gt;The only necessary configuration is to set the agent when running the JVM:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;java -javaagent:opentelemetry-javaagent.jar -jar otel-boot-agent.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The agent supports &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md&quot;&gt;lots of frameworks and libraries&lt;/a&gt;, including Spring Boot, Quarkus, Ktor, Spark, and many others. When the application flow finds a supported framework/library, it logs a span.&lt;/p&gt;
&lt;p&gt;The Agent upholds the standard OpenTelemetry environment variables.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;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
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;OpenTelemetry service name&lt;/li&gt;
&lt;li&gt;OpenTelemetry endpoint; Spring Boot uses HTTP&lt;/li&gt;
&lt;li&gt;Neither metrics nor logs&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's the Jaeger trace when calling the endpoint on the Spring Boot application:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2041424122883509039&quot; alt=&quot;Trace of Spring Boot with OpenTelemetry Agent&quot;&gt;&lt;/p&gt;
&lt;p&gt;And here's the one on Quarkus:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=15781423208460660859&quot; alt=&quot;Trace of Quarkus with OpenTelemetry Agent&quot;&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The Agent outputs the SQL query in both frameworks, &lt;em&gt;i.e.&lt;/em&gt;, &lt;code&gt;SELECT product.* FROM product&lt;/code&gt;. &lt;strong&gt;The Java Agent works out-of-the-box&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;micrometer-tracing-on-spring-boot&quot;&gt;Micrometer Tracing on Spring Boot&lt;/h3&gt;
&lt;p&gt;Spring Boot provides dedicated OpenTelemetry support via &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.micrometer.io/tracing/reference/&quot;&gt;Micrometer Tracing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;gt;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.&lt;/p&gt;
&lt;p&gt;Besides the Micrometer Tracing dependency itself, you need additional ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Spring Boot Actuator&lt;/li&gt;
&lt;li&gt;A bridge–OpenTelemetry&lt;/li&gt;
&lt;li&gt;An exporter–OpenTelemetry as well&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Configuration doesn't follow the OpenTelemetry standard:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  otel-boot-micrometer:
    environment:
      SPRING_APPLICATION_NAME: OTEL Micrometer                       #1-2
      MANAGEMENT_OTLP_TRACING_ENDPOINT: http://jaeger:4318/v1/traces #1-3
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Different values from the OpenTelemetry specification&lt;/li&gt;
&lt;li&gt;The Spring application name serves as the OpenTelemetry service name&lt;/li&gt;
&lt;li&gt;Full path to the API endpoint&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The above setup doesn't register the database call. To fix it, we need an additional dependency: &lt;a rel=&quot;nofollow&quot; href=&quot;https://r2dbc.io/r2dbc-proxy/docs/current-snapshot/docs/html/&quot;&gt;R2DBC Proxy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The new trace contains the database span:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=10716697642535292646&quot; alt=&quot;Trace of Quarkus with OpenTelemetry Agent&quot;&gt;&lt;/p&gt;
&lt;p&gt;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 &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/spring-projects/spring-framework/issues/35185&quot;&gt;GitHub issue&lt;/a&gt; if you're interested.&lt;/p&gt;
&lt;h3 id=&quot;opentelemetry-spring-boot-starter&quot;&gt;OpenTelemetry Spring Boot Starter&lt;/h3&gt;
&lt;p&gt;The OpenTelemetry project provides a &lt;a rel=&quot;nofollow&quot; href=&quot;https://opentelemetry.io/docs/zero-code/java/spring-boot-starter/&quot;&gt;Spring Boot starter&lt;/a&gt;. You need only a single dependency, and like other starters, Spring Boot magic takes care of configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;io.opentelemetry.instrumentation&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;opentelemetry-spring-boot-starter&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=15280324090093701492&quot; alt=&quot;Trace of Spring Boot with the OpenTelemetry Starter&quot;&gt;&lt;/p&gt;
&lt;p&gt;The result is very similar to the previous one, including the not-parallel-but-serial issue.&lt;/p&gt;
&lt;h3 id=&quot;quarkus&quot;&gt;Quarkus&lt;/h3&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;io.quarkus&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;quarkus-opentelemetry&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quarkus prefixes regular OpenTelemetry environment variable names with &lt;code&gt;QUARKUS_&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  otel-quarkus:
    environment:
      QUARKUS_OTEL_SERVICE_NAME: OTEL Quarkus                        #1
      QUARKUS_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: http://jaeger:4317 #2
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;OpenTelemetry service name&lt;/li&gt;
&lt;li&gt;OpenTelemetry endpoint; Quarkus uses gRPC&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Results are as expected:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=6128337141775866164&quot; alt=&quot;Trace of Quarkus&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;discussion&quot;&gt;Discussion&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The complete source code for this post can be found on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ajavageek/otel-jvm&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To go further:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://opentelemetry.io/docs/zero-code/java/agent/&quot;&gt;OpenTelemetry Java Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.frankel.ch/kotlin-coroutines-otel-tracing/&quot;&gt;Kotlin Coroutines and OpenTelemetry tracing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/spring-projects/spring-framework/issues/35185&quot;&gt;Add support for Micrometer context propagation in Kotlin coroutines&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.frankel.ch/opentelemetry-tracing-jvm/&quot;&gt;A Java Geek&lt;/a&gt; on August 3&lt;sup&gt;rd&lt;/sup&gt;, 2025&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/4498/opentelemetry-tracing-on-the-jvm</guid>
<pubDate>Thu, 07 Aug 2025 17:24:20 +0000</pubDate>
</item>
</channel>
</rss>