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