Metrics with Micrometer
AdvancedMicrometer provides a vendor-neutral metrics facade; counters, gauges, and timers feed Prometheus, Datadog, or CloudWatch with dimensional time-series data.
Overview
Micrometer is the metrics instrumentation library bundled with Spring Boot Actuator. It provides a vendor-neutral facade — you instrument your code once using `Counter`, `Timer`, `Gauge`, and `DistributionSummary`, then configure any backend registry (Prometheus, Datadog, CloudWatch, InfluxDB) without changing instrumentation code. `spring-boot-actuator` auto-instruments JVM metrics, HTTP request latency, HikariCP pool stats, Kafka consumer lag, and more. You add custom business metrics (order count, payment latency) by injecting `MeterRegistry`. Metrics are exposed at `/actuator/prometheus` (with `micrometer-registry-prometheus`) and scraped by Prometheus every 15–30 seconds.
Counter, Timer, and Gauge
`Counter` tracks a monotonically increasing count (orders placed, errors). `Timer` measures call duration and count simultaneously (payment API latency). `Gauge` tracks a current value that can go up or down (queue depth, active sessions). Always add **tags** (dimensions) to slice metrics by meaningful attributes — `status`, `region`, `service`. Tags are key-value pairs that become Prometheus labels.
@Service
@RequiredArgsConstructor
class OrderService {
private final MeterRegistry registry;
// Counter — increments on each call
public Order place(CreateOrderRequest req) {
Order order = orderRepo.save(new Order(req));
registry.counter("orders.placed",
"status", "success",
"region", req.getRegion()
).increment();
return order;
}
// Timer — records duration + count
public PaymentResult charge(Order order) {
return Timer.builder("payment.latency")
.tag("provider", "stripe")
.register(registry)
.record(() -> stripeClient.charge(order));
}
}
// Gauge — tracks a current value (register once, reads lambda on each scrape)
@Bean
MeterBinder queueDepthGauge(OrderQueue queue) {
return registry -> Gauge.builder("orders.queue.depth", queue, OrderQueue::size)
.description("Current orders waiting to be processed")
.register(registry);
}Prometheus Scraping and Actuator Endpoint
Add `micrometer-registry-prometheus` to expose `/actuator/prometheus`. Configure Prometheus to scrape this endpoint. The Prometheus naming convention converts Micrometer names: `orders.placed` → `orders_placed_total` (counter), `payment.latency` → `payment_latency_seconds_*` (histogram). Spring Boot auto-instruments dozens of metrics out of the box.
<!-- pom.xml -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
# application.properties
management.endpoints.web.exposure.include=prometheus,health,info
management.metrics.tags.application=order-service # global tag on all metrics
management.metrics.tags.environment=production
# Auto-instrumented metrics (no code required):
# http.server.requests — HTTP request latency + status
# hikaricp.connections.* — connection pool stats
# jvm.memory.used — heap/non-heap usage
# jvm.gc.pause — GC pause durations
# process.cpu.usage — CPU utilisation
# kafka.consumer.fetch.latency.avg — Kafka consumer fetch time
# Prometheus scrape config
scrape_configs:
- job_name: order-service
metrics_path: /actuator/prometheus
static_configs:
- targets: ['order-service:8080']Custom Annotations and @Timed
`@Timed` on a controller or service method auto-records a `Timer` metric without boilerplate. For more control, `@Counted` increments a counter on each invocation. Use `MeterBinder` for infrastructure metrics (external queue depth, cache size) so they are registered at the right lifecycle phase rather than on first request.
// @Timed — auto-instruments method execution time
@RestController
class OrderController {
@PostMapping("/orders")
@Timed(value = "orders.create", extraTags = {"endpoint", "create-order"},
description = "Time to create an order")
ResponseEntity<Order> create(@RequestBody CreateOrderRequest req) {
return ResponseEntity.status(201).body(orderService.place(req));
}
}
// Grafana dashboard PromQL queries:
// Request rate (per second, 5m window)
rate(orders_placed_total[5m])
// P99 HTTP latency (Spring Boot auto-instruments http.server.requests)
histogram_quantile(0.99,
rate(http_server_requests_seconds_bucket{uri="/orders",method="POST"}[5m]))
// Error rate
rate(http_server_requests_seconds_count{status=~"5.*"}[5m])
/ rate(http_server_requests_seconds_count[5m])
// Alert: error rate > 1%
- alert: HighErrorRate
expr: |
rate(http_server_requests_seconds_count{status=~"5.*"}[5m])
/ rate(http_server_requests_seconds_count[5m]) > 0.01
for: 2mKey Points to Remember
- 1Micrometer is a vendor-neutral facade — same code instruments Prometheus, Datadog, CloudWatch
- 2Counter: monotonically increasing (events); Timer: duration + count; Gauge: current snapshot value
- 3Always add tags (dimensions) to slice metrics by status, region, service, method
- 4/actuator/prometheus exposes metrics for Prometheus scraping (requires micrometer-registry-prometheus)
- 5Spring Boot auto-instruments HTTP, JVM, HikariCP, Kafka — dozens of metrics with zero code
- 6management.metrics.tags.application=name adds a global tag to every metric from this service
Interview Questions
Sign in to ask AriaWhat is the difference between a Counter, Timer, and Gauge in Micrometer?
Why should you always add tags to Micrometer metrics?
How does Micrometer allow you to switch from Prometheus to Datadog without changing instrumentation code?
Write a PromQL expression to calculate the P99 latency of POST /orders over the last 5 minutes.
What metrics does Spring Boot auto-instrument without any custom code?
Ask Aria about Metrics with Micrometer
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.