OpenTelemetry Metrics API for Java

This document explains how to use the OpenTelemetry Metrics Java API for manual instrumentation. To learn how to install and configure the OpenTelemetry Java SDK, see Getting started with OpenTelemetry Java.

If you are not familiar with metrics terminology like timeseries or additive/synchronous/asynchronous instruments, read the introduction to OpenTelemetry Metrics first.

Getting started

To get started with metrics, you need to create a Meter using the OpenTelemetry Java API:

java
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.Meter;

// Get the global OpenTelemetry instance
OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();

// Create a meter
Meter meter = openTelemetry.getMeterProvider()
    .meterBuilder("com.example.myapp")
    .setInstrumentationVersion("1.0.0")
    .build();

Using the meter, you can create instruments to measure performance. The simplest Counter instrument looks like this:

java
import io.opentelemetry.api.metrics.LongCounter;

LongCounter requestCounter = meter
    .counterBuilder("http_requests_total")
    .setDescription("Total number of HTTP requests")
    .build();

// Increment the counter
requestCounter.add(1, Attributes.of(
    AttributeKey.stringKey("method"), "GET",
    AttributeKey.stringKey("status"), "200"
));

Counter

Counter is a synchronous instrument that measures additive non-decreasing values, such as requests served, tasks completed, or errors occurred.

java
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributeKey;

// Create a counter for HTTP requests
LongCounter httpRequestsCounter = meter
    .counterBuilder("http_requests_total")
    .setDescription("Total number of HTTP requests")
    .setUnit("1")
    .build();

// Usage in HTTP handler
public void handleRequest(String method, int statusCode) {
    try {
        // Your business logic here
        processRequest();

        // Count successful requests
        httpRequestsCounter.add(1, Attributes.of(
            AttributeKey.stringKey("method"), method,
            AttributeKey.stringKey("status"), String.valueOf(statusCode)
        ));
    } catch (Exception ex) {
        // Count failed requests
        httpRequestsCounter.add(1, Attributes.of(
            AttributeKey.stringKey("method"), method,
            AttributeKey.stringKey("status"), "500"
        ));
        throw ex;
    }
}

UpDownCounter

UpDownCounter is a synchronous instrument that measures values that can go up or down, such as active connections, queue size, or memory usage.

java
import io.opentelemetry.api.metrics.LongUpDownCounter;

// Create an up-down counter for active connections
LongUpDownCounter activeConnectionsCounter = meter
    .upDownCounterBuilder("db_connections_active")
    .setDescription("Number of active database connections")
    .setUnit("1")
    .build();

// Usage in connection management
public class ConnectionPool {
    public Connection getConnection() {
        Connection connection = createConnection();

        // Increment when connection is created
        activeConnectionsCounter.add(1);

        return connection;
    }

    public void releaseConnection(Connection connection) {
        connection.close();

        // Decrement when connection is released
        activeConnectionsCounter.add(-1);
    }
}

Histogram

Histogram is a synchronous instrument that records a distribution of values, perfect for measuring request durations, response sizes, or query execution times.

java
import io.opentelemetry.api.metrics.DoubleHistogram;

// Create a histogram for request duration
DoubleHistogram requestDurationHistogram = meter
    .histogramBuilder("http_request_duration_seconds")
    .setDescription("Duration of HTTP requests in seconds")
    .setUnit("s")
    .build();

// Usage with timing
public void handleRequest(String method, String endpoint) {
    long startTime = System.nanoTime();

    try {
        // Your business logic here
        processRequest();
    } finally {
        long endTime = System.nanoTime();
        double durationSeconds = (endTime - startTime) / 1_000_000_000.0;

        // Record the duration
        requestDurationHistogram.record(durationSeconds, Attributes.of(
            AttributeKey.stringKey("method"), method,
            AttributeKey.stringKey("endpoint"), endpoint
        ));
    }
}

Gauge

Gauge is a synchronous instrument that records the current value of something at the time of measurement.

java
import io.opentelemetry.api.metrics.DoubleGauge;

// Create a gauge for temperature
DoubleGauge temperatureGauge = meter
    .gaugeBuilder("room_temperature_celsius")
    .setDescription("Current room temperature")
    .setUnit("°C")
    .build();

// Usage when temperature changes
public void onTemperatureChanged(double newTemperature, String room) {
    // Record the current temperature
    temperatureGauge.set(newTemperature, Attributes.of(
        AttributeKey.stringKey("room"), room
    ));
}

Asynchronous Instruments

ObservableCounter

ObservableCounter is an asynchronous instrument that measures additive non-decreasing values collected via a callback.

java
import io.opentelemetry.api.metrics.ObservableLongCounter;

// Create an observable counter for total processed orders
ObservableLongCounter totalOrdersCounter = meter
    .counterBuilder("orders_total")
    .setDescription("Total number of processed orders")
    .setUnit("1")
    .buildWithCallback(measurement -> {
        long totalOrders = getTotalOrdersFromDatabase();
        measurement.record(totalOrders);
    });

private long getTotalOrdersFromDatabase() {
    // Query your database to get the current total
    return orderService.getTotalOrderCount();
}

ObservableUpDownCounter

ObservableUpDownCounter is an asynchronous instrument that measures additive values that can increase or decrease.

java
import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;

// Create observable up-down counter for queue size
ObservableLongUpDownCounter queueSizeCounter = meter
    .upDownCounterBuilder("queue_size")
    .setDescription("Current number of messages in queue")
    .setUnit("1")
    .buildWithCallback(measurement -> {
        long queueSize = messageQueue.size();
        measurement.record(queueSize);
    });

ObservableGauge

ObservableGauge is an asynchronous instrument that measures non-additive values that can go up and down.

java
import io.opentelemetry.api.metrics.ObservableDoubleGauge;

// Create an observable gauge for memory usage
ObservableDoubleGauge memoryUsageGauge = meter
    .gaugeBuilder("jvm_memory_usage_bytes")
    .setDescription("Current JVM memory usage")
    .setUnit("By")
    .buildWithCallback(measurement -> {
        Runtime runtime = Runtime.getRuntime();
        long usedMemory = runtime.totalMemory() - runtime.freeMemory();
        measurement.record(usedMemory);
    });

// For multiple measurements with attributes
ObservableDoubleGauge systemStatsGauge = meter
    .gaugeBuilder("system_stats")
    .setDescription("Various system statistics")
    .buildWithCallback(measurement -> {
        MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();

        // Heap memory usage
        long heapUsed = memoryBean.getHeapMemoryUsage().getUsed();
        measurement.record(heapUsed, Attributes.of(
            AttributeKey.stringKey("memory_type"), "heap"
        ));

        // Non-heap memory usage
        long nonHeapUsed = memoryBean.getNonHeapMemoryUsage().getUsed();
        measurement.record(nonHeapUsed, Attributes.of(
            AttributeKey.stringKey("memory_type"), "non_heap"
        ));
    });

Complete example

java
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.metrics.*;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributeKey;

public class MetricsExample {
    private static final Meter meter = GlobalOpenTelemetry.get()
        .getMeterProvider()
        .meterBuilder("com.example.myapp")
        .setInstrumentationVersion("1.0.0")
        .build();

    // Synchronous instruments
    private static final LongCounter requestCounter = meter
        .counterBuilder("http_requests_total")
        .setDescription("Total HTTP requests")
        .build();

    private static final DoubleHistogram requestDuration = meter
        .histogramBuilder("http_request_duration_seconds")
        .setDescription("HTTP request duration")
        .setUnit("s")
        .build();

    // Asynchronous instruments
    private static final ObservableDoubleGauge jvmMemoryGauge = meter
        .gaugeBuilder("jvm_memory_usage_bytes")
        .setDescription("JVM memory usage")
        .setUnit("By")
        .buildWithCallback(MetricsExample::recordJvmMemory);

    public static void handleHttpRequest(String method, String path) {
        long startTime = System.nanoTime();

        try {
            // Process request
            processRequest();

            // Record successful request
            recordRequest(method, path, "200", startTime);
        } catch (Exception e) {
            // Record failed request
            recordRequest(method, path, "500", startTime);
            throw e;
        }
    }

    private static void recordRequest(String method, String path, String status, long startTime) {
        Attributes attributes = Attributes.of(
            AttributeKey.stringKey("method"), method,
            AttributeKey.stringKey("path"), path,
            AttributeKey.stringKey("status"), status
        );

        // Increment counter
        requestCounter.add(1, attributes);

        // Record duration
        double duration = (System.nanoTime() - startTime) / 1_000_000_000.0;
        requestDuration.record(duration, attributes);
    }

    private static void recordJvmMemory(ObservableDoubleMeasurement measurement) {
        Runtime runtime = Runtime.getRuntime();
        long usedMemory = runtime.totalMemory() - runtime.freeMemory();
        measurement.record(usedMemory);
    }

    private static void processRequest() {
        // Your business logic
    }
}

OpenTelemetry APM

Uptrace is a DataDog competitor that supports distributed tracing, metrics, and logs. You can use it to monitor applications and troubleshoot issues.

Uptrace comes with an intuitive query builder, rich dashboards, alerting rules with notifications, and integrations for most languages and frameworks.

Uptrace can process billions of spans and metrics on a single server and allows you to monitor your applications at 10x lower cost.

In just a few minutes, you can try Uptrace by visiting the cloud demo (no login required) or running it locally with Docker. The source code is available on GitHub.

What's next?