Instrumenting Java Apps with OpenTelemetry Metrics
This article provides a detailed guide on integrating OpenTelemetry metrics into your Java application.
It explores key concepts, including instrumenting your application with various metric types, monitoring HTTP request activity, and exporting metrics to visualization tools.
Let's get started!
Prerequisites
- Prior experience with Java and Spring Boot, along with a recent JDK installed
- Maven or Gradle for dependency management
- Familiarity with Docker and Docker Compose
- Basic understanding of how OpenTelemetry works
Step 1 — Setting up the demo project
To demonstrate OpenTelemetry instrumentation in Java applications, let's set up a simple "Hello World" Spring Boot application along with the Prometheus server for visualizing metrics.
First, create a new Spring Boot project. The easiest way is to use Spring Initializr. Select:
- Maven or Gradle (we'll use Maven for this tutorial)
- Java 23
- Spring Boot 3.4.3
- Dependencies: Spring Web
Download and extract the project, then open it in your preferred IDE.
Here's the initial application class:
This app exposes two endpoints: / returns a simple "Hello world!" message, and
/metrics endpoint that will eventually expose the instrumented metrics. The
empty metrics endpoint is a placeholder we'll expand later.
Create a compose.yaml file in your project root to set up both our application
and Prometheus server in Docker:
This configuration sets up two services: our Spring Boot application and Prometheus. The volumes ensure code changes are reflected in real-time and data is persisted.
Create a Dockerfile for the Spring Boot application:
This Dockerfile uses the Eclipse Temurin JDK 17 image and configures Maven to run our Spring Boot application.
Create a prometheus.yml configuration file to tell Prometheus where to find
metrics:
This configuration tells Prometheus to scrape metrics from our app every 10 seconds. In Docker Compose networking, "app" refers to our Spring Boot container.
Before starting the services, create an .env file:
Launch both services with:
To confirm that the Spring Boot application is running, send a request to the root endpoint:
This should return:
Step 2 — Installing OpenTelemetry dependencies
Now let's add the OpenTelemetry dependencies to your Spring Boot application. Unlike Prometheus with Micrometer, OpenTelemetry provides a vendor-neutral API for observability that includes not just metrics, but also traces and logs.
Update your pom.xml to include these dependencies:
These dependencies provide:
- The OpenTelemetry API for defining metrics
- The SDK that implements the API
- The Prometheus exporter for exposing metrics
- Semantic conventions for standardized naming
Next, create a configuration class to set up OpenTelemetry with Prometheus export:
This configuration:
- Creates a resource that identifies your application with a name and version
- Sets up a Prometheus HTTP server to expose metrics on port 9464
- Configures a meter provider that will collect metrics and send them to Prometheus
- Builds and registers a global OpenTelemetry instance for your application to use
Update your prometheus.yml configuration to scrape metrics from the
OpenTelemetry exporter:
Notice we're now targeting port 9464 where the OpenTelemetry Prometheus exporter will expose metrics.
Rebuild your application:
Now when you visit http://localhost:9464/metrics, you'll see the default
metrics that OpenTelemetry automatically collects.
Step 3 — Implementing a Counter Metric
Let's implement our first custom metric - a counter to track the total number of HTTP requests to our application. A counter is a cumulative metric that only increases over time or resets to zero (like a car's odometer).
Create a metrics service to define and manage your OpenTelemetry metrics:
In this service:
- We create a meter named "com.example.demo" as a namespace for our metrics
- We define a counter named "http.requests.total" with a clear description
- We provide a method to increment the counter with an attribute that identifies our app
Next, create a web filter that will increment the counter for each request:
This filter:
- Extends Spring's
OncePerRequestFilterto ensure it runs exactly once per request - Injects our metrics service
- Increments the request counter for every HTTP request
- Continues the filter chain to process the request normally
After making several requests to your application, visiting the /metrics
endpoint will show:
The value will keep increasing as more requests are processed. This is perfect for tracking total events, throughput, or error counts.
Step 4 — Implementing a Gauge Metric
Unlike counters that only increase, gauges represent values that can fluctuate up and down, like current temperature or memory usage. In OpenTelemetry, we implement gauges using an UpDownCounter.
Let's add a gauge to track the number of active requests in our application:
The key additions here:
- We create an UpDownCounter that works like a gauge
- We provide methods to both increment and decrement the value
- This lets us track a value that rises and falls over time
Update the filter to track active requests:
This filter now:
- Increments the active request gauge when a request begins
- Ensures we decrement it when the request ends (even if errors occur) using a try-finally block
To observe the gauge in action, let's add some random delay to the root endpoint:
This delay simulates processing time and allows multiple concurrent requests to accumulate, making our active request gauge more interesting to observe.
Step 5 — Implementing a Histogram Metric
Histograms are essential for understanding the distribution of values like request durations. They track not just a single number but the spread of values across predefined buckets.
Update your MetricsService to include a histogram:
Key points about the histogram:
- We use a
DoubleHistogrambecause durations are typically fractional values - We set a unit of "s" (seconds) to make the metric more understandable
- The histogram automatically distributes values across predefined buckets
Update the filter to record request durations:
This enhancement:
- Takes a high-precision timestamp before processing the request
- Calculates the duration after the request completes
- Converts from nanoseconds to seconds (dividing by 1 billion)
- Records the duration in our histogram
Histograms are particularly useful for analyzing latency patterns and setting SLOs (Service Level Objectives). In Prometheus, you can calculate percentiles (e.g., p95, p99) to understand the experience of most users while ignoring outliers.
Step 6 — Adding Context to Metrics with Attributes
Attributes (labels in Prometheus terminology) let you segment metrics by different dimensions like endpoint, HTTP method, or status code. This provides more granular analysis.
Let's enhance our metrics with additional context:
This enhanced filter:
- Creates attributes containing the URI path and HTTP method
- Adds the HTTP status code to histogram attributes after the response is generated
- Allows you to analyze metrics by endpoint, method, and response status
Update the MetricsService to accept attributes in its methods:
With these attributes, you can now answer questions like:
- Which endpoints have the highest traffic?
- Which endpoints have the slowest response times?
- What percentage of requests to a specific endpoint result in errors?
Be careful not to add too many unique attribute combinations, as this can lead to "cardinality explosion" that impacts performance.
Step 7 — Implementing External API Monitoring
Let's create a service to monitor external API calls using OpenTelemetry:
This service:
- Creates a histogram to track API call durations
- Wraps the API call with timing logic
- Records the duration with attributes identifying the external API and endpoint
Add a controller to use this service:
This pattern lets you monitor external dependencies, identify performance bottlenecks, and set alerts for problematic third-party services.
Final thoughts
In this tutorial, we explored how to integrate OpenTelemetry metrics into a Spring Boot application. We implemented counters for tracking cumulative values, gauges for fluctuating measurements, and histograms for analyzing distributions.
OpenTelemetry offers several advantages over traditional monitoring approaches: it provides vendor-neutral instrumentation, combines metrics with tracing and logging, and implements standardized conventions across different programming languages.
For a production deployment, consider setting up an OpenTelemetry Collector to process and route your telemetry data to different backends, and connect your metrics to observability tools like Better Stack to create comprehensive dashboards.
Remember to focus on actionable metrics that directly tie to user experience and business goals, and create alerts for critical thresholds to ensure proactive monitoring of your applications.