Backend Development 29 min read

Practical Guide to Micrometer: JVM Metrics Framework with Spring Boot, Prometheus, and Grafana

This article introduces the Micrometer metrics library for JVM applications, explains its core Meter types and registries, demonstrates how to instrument Spring Boot services with counters, timers, gauges, and other meters, and shows end‑to‑end integration with Prometheus and Grafana for monitoring.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Practical Guide to Micrometer: JVM Metrics Framework with Spring Boot, Prometheus, and Grafana

Micrometer is a vendor‑agnostic metrics library for JVM applications. It defines the Meter abstraction (Timer, Counter, Gauge, DistributionSummary, LongTaskTimer, FunctionCounter, FunctionTimer, TimeGauge) and a MeterRegistry that creates and stores meters. Common registry implementations include SimpleMeterRegistry (in‑memory only), CompositeMeterRegistry (aggregates multiple registries), and the global static registry accessed via Metrics .

MeterRegistry registry = new SimpleMeterRegistry();
Counter counter = registry.counter("counter");
counter.increment();

Composite registries start empty; meters added before a concrete registry are ineffective until a real registry is added.

CompositeMeterRegistry composite = new CompositeMeterRegistry();
Counter compositeCounter = composite.counter("counter");
compositeCounter.increment(); // no effect
SimpleMeterRegistry simple = new SimpleMeterRegistry();
composite.add(simple);
compositeCounter.increment(); // now works

The global registry simplifies usage via static methods:

Metrics.addRegistry(new SimpleMeterRegistry());
Counter counter = Metrics.counter("counter", "tag-1", "tag-2");
counter.increment();

Micrometer uses Tag objects to add dimensions to a meter. Tags are key‑value pairs and must appear in pairs (Key=Value). Naming conventions can be customized with a NamingConvention implementation.

registry.config().namingConvention(myCustomNamingConvention);

Examples of naming conversion for Prometheus, Atlas, Graphite, and InfluxDB are shown, illustrating how a dot‑separated name like http.server.requests is transformed for each backend.

Key meter types and typical usage:

Counter

A monotonically increasing integer used for counting events such as orders or HTTP requests. Example with tags for channel and timestamp:

Metrics.counter("order.create", "channel", order.getChannel(), "createTime", FORMATTER.format(order.getCreateTime())).increment();

FunctionCounter

Wraps a ToDoubleFunction so the counter value is derived from an external object (e.g., an AtomicInteger ).

FunctionCounter.builder("functionCounter", n, AtomicInteger::get)
    .baseUnit("function")
    .description("functionCounter")
    .tag("createOrder", "CHANNEL-A")
    .register(registry);

Timer

Measures the duration of short‑lived events. It can record via a Runnable , Callable , or a supplier, and also provides a Sample API for manual start/stop.

Timer timer = Metrics.timer("method.cost.time", "method.name", method.getName());
 timer.record(() -> createOrder(order));
Timer.Sample sample = Timer.start(registry);
 // business logic
 sample.stop(registry.timer("my.timer", "response", response.status()));

FunctionTimer

Similar to Timer but obtains count and total time from functions, useful for monitoring libraries such as caches.

FunctionTimer.builder("functionTimer", holder, p -> totalCount.get(), p -> totalTimeNanos.get(), TimeUnit.NANOSECONDS)
    .register(new SimpleMeterRegistry());

LongTaskTimer

Tracks long‑running tasks that may span many seconds or minutes. It can be used directly or via Spring’s @Timed and @Scheduled annotations.

LongTaskTimer longTaskTimer = meterRegistry.more().longTaskTimer("longTaskTimer");
 longTaskTimer.record(() -> { /* task */ });

Gauge & TimeGauge

Provides a snapshot of a value that can go up and down, such as collection size, memory usage, or a custom Number . Gauges can be created from collections, maps, or atomic numbers.

Gauge gauge = registry.gauge("listGauge", Collections.emptyList(), new ArrayList<>(), List::size);
AtomicInteger n = registry.gauge("numberGauge", new AtomicInteger(0));
TimeGauge timeGauge = TimeGauge.builder("timeGauge", count, TimeUnit.SECONDS, AtomicInteger::get).register(registry);

DistributionSummary

Records the distribution of non‑time‑based values (e.g., payload size). It can be built with optional description, base unit, tags, and scaling.

DistributionSummary summary = DistributionSummary.builder("response.size")
    .description("size of HTTP responses")
    .baseUnit("bytes")
    .tags("region", "test")
    .register(registry);

Integration with Spring Boot is straightforward because spring-boot-starter-actuator already pulls in Micrometer. Adding the micrometer-registry-prometheus dependency enables a /actuator/prometheus endpoint that Prometheus can scrape.

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <version>1.1.0</version>
</dependency>

Sample Spring components (entity, controller, service, AOP aspect) illustrate how to record counters and timers for order processing and message sending.

@RestController
public class OrderController {
    @Autowired private OrderService orderService;
    @PostMapping("/order")
    public ResponseEntity
createOrder(@RequestBody Order order) {
        return ResponseEntity.ok(orderService.createOrder(order));
    }
}

Application YAML config enables all actuator endpoints on a separate management port:

server:
  port: 9091
management:
  server:
    port: 10091
  endpoints:
    web:
      exposure:
        include: '*'
  base-path: /management

Prometheus is installed manually, configured to scrape the Spring Boot /management/prometheus endpoint, and started with its default storage path.

wget https://github.com/prometheus/prometheus/releases/download/v2.5.0/prometheus-2.5.0.linux-amd64.tar.gz
tar xvfz prometheus-*.tar.gz
./prometheus --config.file=prometheus.yml
scrape_configs:
  - job_name: 'prometheus'
    metrics_path: /management/prometheus
    static_configs:
      - targets: ['localhost:10091']

Grafana is installed via RPM, started on port 3000, and configured with a Prometheus data source. Dashboards can then visualize counters, timers, and other meters using PromQL queries.

wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.3.4-1.x86_64.rpm
sudo yum localinstall grafana-5.3.4-1.x86_64.rpm
service grafana-server start

The article also contains promotional sections encouraging readers to follow a public account for free resources, but the technical content remains a comprehensive tutorial on using Micrometer for JVM monitoring.

JavamonitoringmetricsPrometheusSpring BootGrafanaMicrometer
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.