Introduction

As distributed systems become the norm in modern application development, monitoring and tracing these systems is essential for understanding the performance and behavior of your applications. OpenTelemetry is an open-source observability framework that provides tools to instrument, generate, collect, and export telemetry data (such as traces, metrics, and logs) from your application. In this article, we’ll explore how to implement OpenTelemetry tracing in a Spring Boot application. By the end of this guide, you will have a clear understanding of how to set up tracing in your Spring Boot application, configure it, and view traces in a tracing backend.

What is OpenTelemetry?

OpenTelemetry is a set of APIs, libraries, agents, and instrumentation that provide a standard way to collect and export telemetry data. It is a merger of two projects: OpenTracing and OpenCensus. The main goal of OpenTelemetry is to make observability data (traces, metrics, and logs) easy to collect, visualize, and analyze. This data helps developers understand how their applications are performing and where bottlenecks or errors might occur.

Why Use OpenTelemetry?

  • Vendor-neutral: OpenTelemetry supports multiple backends, so you’re not locked into a specific vendor.
  • Unified APIs: It provides consistent APIs across multiple languages and platforms.
  • Community-driven: Being part of the Cloud Native Computing Foundation (CNCF), it has strong community support and contributions.
  • Extensible: You can extend and customize OpenTelemetry to fit your specific needs.

Setting Up a Spring Boot Project

Let’s start by setting up a basic Spring Boot application with OpenTelemetry tracing.

Create a New Spring Boot Project

If you haven’t already, create a new Spring Boot project using Spring Initializr. For this example, use the following settings:

  • Project: Maven Project
  • Language: Java
  • Spring Boot Version: 3.0.0 (or latest)
  • Dependencies:
    • Spring Web
    • Spring Boot Actuator

Once you’ve downloaded the project, open it in your preferred IDE.

Add OpenTelemetry Dependencies

To enable OpenTelemetry tracing, you’ll need to add the necessary dependencies to your pom.xml file:

xml

<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>1.17.0</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<version>1.17.0</version>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
<version>1.17.0</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>1.17.0</version>
</dependency>

These dependencies provide the core OpenTelemetry API, the SDK for configuring and exporting telemetry data, and the Spring Boot starter for automatic instrumentation.

Configuring OpenTelemetry

Configure the Tracer

OpenTelemetry requires a Tracer to create spans, which represent individual units of work within your application. To configure the Tracer, create a configuration class in your Spring Boot application.

java

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenTelemetryConfig {@Bean
public Tracer tracer() {
OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint(“http://localhost:4317”) // Change to your OTLP endpoint
.build();SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
.build();OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.buildAndRegisterGlobal();return openTelemetrySdk.getTracer(“spring-boot-example”);
}
}

In this configuration:

  • We define a Tracer bean that is used throughout the application.
  • We set up a BatchSpanProcessor that processes spans in batches for more efficient exporting.
  • The OtlpGrpcSpanExporter is configured to send traces to an OpenTelemetry Protocol (OTLP) endpoint, typically your observability backend.

Instrumenting the Application

With the tracer in place, you can now instrument your application to create and record spans.

Example: Controller with Tracing

Let’s modify a simple Spring Boot controller to create custom spans.

java

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(“/api”)
public class DemoController {@Autowired
private Tracer tracer;@GetMapping(“/hello”)
public String hello() {
Span span = tracer.spanBuilder(“hello-span”).startSpan();
try {
span.setAttribute(“http.method”, “GET”);
span.setAttribute(“http.url”, “/api/hello”);// Simulate some work
Thread.sleep(100);
return “Hello, OpenTelemetry!”;
} catch (InterruptedException e) {
span.recordException(e);
return “Error”;
} finally {
span.end();
}
}
}

In this example, we manually create a span for the /hello endpoint. The span has attributes that provide additional metadata, such as the HTTP method and URL. The span.end() method ensures the span is properly closed, even if an exception occurs.

Automatically Instrumenting with OpenTelemetry

Spring Boot applications can be automatically instrumented using the OpenTelemetry Java Agent. This eliminates the need to manually create spans for many common operations, such as HTTP requests and database queries.

Adding the OpenTelemetry Java Agent

To use the Java agent, download the agent JAR and add it as a JVM argument when running your Spring Boot application.

bash

java -javaagent:/path/to/opentelemetry-javaagent.jar -jar target/demo-0.0.1-SNAPSHOT.jar

With the agent in place, OpenTelemetry will automatically instrument various components of your Spring Boot application, such as Spring Web, Spring WebFlux, and JDBC.

Exporting Traces to a Backend

Setting Up a Tracing Backend

To visualize and analyze the traces, you need a tracing backend. Popular choices include:

  • Jaeger
  • Zipkin
  • Grafana Tempo
  • OpenTelemetry Collector

For this example, we’ll use Jaeger.

Running Jaeger

You can easily run Jaeger using Docker:

bash

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:1.22

Jaeger’s UI will be available at http://localhost:16686. By default, traces from the OpenTelemetry Java agent or SDK will be sent to localhost:4317, which you can configure accordingly.

Visualizing Traces in Jaeger

Once your application is running and sending traces, open the Jaeger UI and search for traces from your application. You should see a list of traces, which you can explore to view the various spans and their relationships.

Each span will show detailed information, such as:

  • Start and end time
  • Duration
  • Tags/attributes
  • Logs
  • Parent/child relationships

This information is invaluable for diagnosing issues and understanding the flow of requests through your distributed system.

Advanced Configuration and Optimization

Sampling

In high-throughput applications, tracing every request might not be feasible. OpenTelemetry allows you to configure sampling to control the rate of trace data collection.

For example, you can modify your tracer configuration to include a sampling strategy:

java

import io.opentelemetry.sdk.trace.samplers.Sampler;

@Bean
public Tracer tracer() {
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.setSampler(Sampler.traceIdRatioBased(0.5)) // 50% sampling rate
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
.build();

}

Custom Exporters

If you need to export traces to a custom backend or format, you can implement your own SpanExporter. This gives you the flexibility to integrate with other systems or store traces in a proprietary format.

Context Propagation

OpenTelemetry supports context propagation, allowing you to trace requests across multiple services. This is essential for distributed systems where a single request might traverse multiple microservices. Spring Boot integrates with OpenTelemetry to automatically propagate context between services via HTTP headers.

Conclusion

OpenTelemetry tracing provides a powerful and flexible way to monitor and observe your Spring Boot applications. By collecting detailed trace data, you gain insights into your application’s performance, bottlenecks, and error patterns. Whether you’re building a simple monolithic application or a complex microservices architecture, OpenTelemetry helps you understand and optimize your system.

This guide covered the basics of setting up OpenTelemetry tracing in a Spring Boot application, configuring a tracer, instrumenting your code, and exporting traces to a backend like Jaeger. We also touched on more advanced topics such as sampling and custom exporters.

As you continue to develop and scale your applications, consider leveraging the full suite of OpenTelemetry capabilities, including metrics and logs, to achieve comprehensive observability. The flexibility and extensibility of OpenTelemetry ensure that it can grow with your needs, providing a robust foundation for monitoring and improving your applications over time.