Microservices distributed tracing with Jaeger and Micronaut
Use Jaeger distributed tracing to investigate the behaviour of your Micronaut apps.
Authors: Sergio del Amo
Micronaut Version: 2.5.0
1. Getting Starter
In this guide, we are going to integrate Jaeger in a Micronaut app composed of three microservices.
As on-the-ground microservice practitioners are quickly realizing, the majority of operational problems that arise when moving to a distributed architecture are ultimately grounded in two areas: networking and observability. It is simply an orders of magnitude larger problem to network and debug a set of intertwined distributed services versus a single monolithic application.
You will discover how Micronaut eases Jaeger integration.
2. What you will need
To complete this guide, you will need the following:
-
Some time on your hands
-
A decent text editor or IDE
-
JDK 1.8 or greater installed with
JAVA_HOME
configured appropriately
3. Solution
We recommend that you follow the instructions in the next sections and create the app step by step. However, you can go right to the completed example.
-
Download and unzip the source
4. Writing the app
To learn more about this sample app read Consul and Micronaut - Microservices service discovery guide. The application contains three microservices.
-
bookcatalogue
- It returns a list of books. It uses a domain consisting of a book name and isbn. -
bookinventory
- It exposes an endpoint to check whether a book has sufficient stock to fulfil an order. It uses a domain consisting of a stock level and isbn. -
bookrecommendation
- It consumes previous services and exposes and endpoint which recommends book names which are in stock.
The bookcatalogue
service consumes endpoints exposed by the other services. The following image illustrates the application flow:
A request to bookrecommendation
(http://localhost:8080/books
) triggers several requests through our microservices mesh.
If you are using Java or Kotlin and IntelliJ IDEA, make sure you have enabled annotation processing.

5. Jaeger and Micronaut
5.1. Install Jaeger via Docker
The quickest way to start Jaeger is via Docker:
$ docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 14250:14250 \
-p 9411:9411 \
jaegertracing/all-in-one:latest
5.2. Book catalogue
Add tracing
dependency.
implementation("io.micronaut:micronaut-tracing")
To send tracing spans to Jaeger the minimal configuration requires you add the following dependency:
runtimeOnly("io.jaegertracing:jaeger-thrift")
Append to bookcatalogue
service application.yml
the following snippet:
tracing:
jaeger:
enabled: true
sampler:
probability: 1 (1)
1 | Trace 100% of requests. |
In production, you will probably want to trace a smaller percentage of the requests. However, in order to keep this tutorial easy, we set it to trace 100%.
Disable distributed tracing in tests:
tracing:
jaeger:
enabled: false
5.3. Book inventory
Add tracing
dependency.
implementation("io.micronaut:micronaut-tracing")
To send tracing spans to Jaeger the minimal configuration requires you add the following dependency:
runtimeOnly("io.jaegertracing:jaeger-thrift")
Append to bookinventory
service application.yml
the following snippet:
tracing:
jaeger:
enabled: true
sampler:
probability: 1 (1)
1 | Trace 100% of requests. |
In production, you will probably want to trace a smaller percentage of the requests. However, in order to keep this tutorial easy, we set it to trace 100%.
Disable distributed tracing in tests:
tracing:
jaeger:
enabled: false
Annotate the method with @ContinueSpan
and the parameter with @SpanTag
:
package example.micronaut;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import io.micronaut.tracing.annotation.ContinueSpan;
import io.micronaut.tracing.annotation.SpanTag;
import javax.validation.constraints.NotBlank;
import java.util.Optional;
@Controller("/books")
public class BooksController {
@Produces(MediaType.TEXT_PLAIN)
@Get("/stock/{isbn}")
@ContinueSpan (1)
public Boolean stock(@SpanTag("stock.isbn") @NotBlank String isbn) { (2)
return bookInventoryByIsbn(isbn).map(bi -> bi.getStock() > 0).orElse(null);
}
private Optional<BookInventory> bookInventoryByIsbn(String isbn) {
if (isbn.equals("1491950358")) {
return Optional.of(new BookInventory(isbn, 4));
} else if (isbn.equals("1680502395")) {
return Optional.of(new BookInventory(isbn, 0));
}
return Optional.empty();
}
}
1 | The @ContinueSpan annotation will continue an existing span, wrapping the method call or reactive type. |
2 | The @SpanTag annotation can be used on method arguments to include the value of each argument within a Span’s tags. When you use @SpanTag you need
either to annotate the method with @NewSpan or @ContinueSpan . |
5.4. Book recommendation
Add tracing
dependency.
implementation("io.micronaut:micronaut-tracing")
To send tracing spans to Jaeger the minimal configuration requires you add the following dependency:
runtimeOnly("io.jaegertracing:jaeger-thrift")
Append to bookrecommendation
service application.yml
the following snippet:
tracing:
jaeger:
enabled: true
sampler:
probability: 1 (1)
1 | Trace 100% of requests. |
In production, you will probably want to trace a smaller percentage of the requests. However, in order to keep this tutorial easy, we set it to trace 100%.
Disable distributed tracing in tests:
tracing:
jaeger:
enabled: false
6. Running the app
Run bookcatalogue
microservice:
To run the application execute ./gradlew run
.
...
14:28:34.034 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8081
Run bookinventory
microservice:
To run the application execute ./gradlew run
.
...
14:31:13.104 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 506ms. Server Running: http://localhost:8082
Run bookrecommendation
microservice:
To run the application execute ./gradlew run
.
...
14:31:57.389 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 523ms. Server Running: http://localhost:8080
You can run a cURL command to test the whole application:
$ curl http://localhost:8080/books
[{"name":"Building Microservices"}
You can then navigate to http://localhost:16686 to access the Jaeger UI.
The previous request generates a traces composed by 9 spans.

In the previous image, you can see that:
-
Whenever a Micronaut HTTP client executes a new network request, it creates a new Span.
-
Whenever a Micronaut Server receives a request, it creates a new Span.
The stock.isbn
tags that we configured with @SpanTag
are present.
Moreover, you can see the requests to bookinventory
are done in parallel.
7. Next steps
As you have seen in this tutorial, without any annotations you get distributing tracing up-and-running fast with Micronaut.
Micronaut includes several annotations to give you more flexibility. We introduced the @ContinueSpan
, @SpanTag
annotations. Also, you have at your disposal the @NewSpan
annotation which will create a new span, wrapping the method call or reactive type.
Make sure to read more about Tracing with Jaeger inside Micronaut.
8. Help with Micronaut
Object Computing, Inc. (OCI) sponsored the creation of this Guide. A variety of consulting and support services are available.