In the Getting Started with Spring Boot Actuator guide, we covered health checks and exposing endpoints. The next phase is “I want to properly monitor metrics in production.”
In this article, we’ll assemble a Micrometer → Prometheus → Grafana monitoring pipeline locally, and go all the way to verifying custom metrics on a dashboard.
Overall Flow
The setup consists of three components.
- Spring Boot + Micrometer auto-instruments JVM and HTTP metrics and exposes them at
/actuator/prometheus - Prometheus periodically scrapes the endpoint and stores the data as time series
- Grafana renders dashboards using Prometheus as a data source
Adding Dependencies
Two dependencies are required: spring-boot-starter-actuator and micrometer-registry-prometheus. If you want to use the @Timed annotation described later, you’ll also need spring-boot-starter-aop.
// build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.springframework.boot:spring-boot-starter-aop' // @Timedを使う場合に必要
}
For Maven, use the following.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- @Timedを使う場合に必要 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
In Spring Boot 3.x, the group ID of micrometer-registry-prometheus is io.micrometer. You can leave version management to the Spring Boot BOM.
Exposing the Endpoint in application.yml
The endpoint is not exposed over the web by default, so you need to specify it explicitly in application.yml.
management:
endpoints:
web:
exposure:
include: health, info, prometheus
# YAML配列形式でも同じ動作: include: [health, info, prometheus]
After starting the application, run curl http://localhost:8080/actuator/prometheus and you’ll get a response in OpenMetrics format like this.
# HELP jvm_memory_used_bytes The amount of used memory
# TYPE jvm_memory_used_bytes gauge
jvm_memory_used_bytes{area="heap",id="G1 Eden Space"} 2.3068672E7
Starting Prometheus and Grafana with Docker Compose
Prepare two files: docker-compose.yml and prometheus.yml.
# docker-compose.yml
services:
prometheus:
image: prom/prometheus:v2.51.0
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus # データ永続化(再起動後もメトリクス履歴が消えない)
grafana:
image: grafana/grafana:10.4.0
ports:
- "3000:3000"
environment:
# ※ローカル開発専用。本番環境では必ず強いパスワードに変更してください
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
volumes:
prometheus_data:
grafana_data:
By defining both prometheus_data and grafana_data, your data survives container restarts. The image versions are pinned to those current at the time of writing.
# prometheus.yml
global:
scrape_interval: 30s # ローカル開発は30s、本番は15sが一般的
scrape_configs:
- job_name: 'spring-boot'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['host.docker.internal:8080']
# Linuxの場合:
# - Docker Desktop for Linux: host.docker.internalが使用可能
# - Docker Engine直接インストール: 172.17.0.1:8080(docker network inspect bridge でGateway IP確認)
# または network_mode: host 利用時は localhost:8080
Once you’ve started everything with docker compose up -d, open http://localhost:9090/targets and confirm that the State of the spring-boot target is UP.
Registering Prometheus in Grafana
Log in to Grafana at http://localhost:3000 (admin/admin). You may see a 502 right after startup, but waiting a few seconds and reloading resolves it.
- Go to Connections > Data sources > Add new data source and select Prometheus
- Enter
http://prometheus:9090as the URL (name resolution within the docker network) - Click Save & Test and confirm “Data source is working”
Importing Official Dashboards
From Dashboards > Import, you can get a fully featured dashboard just by entering an ID.
- 4701 is the JVM Micrometer dashboard (heap memory, GC, thread count, and so on). It works completely with Prometheus alone.
- 17175 is Spring Boot Observability (an integrated dashboard supporting Spring Boot 3.x). The log-related panels show No data because Loki isn’t configured, but the JVM metrics and HTTP request panels display correctly.
Note: The frequently recommended 10280 (Spring Boot 2.1 Statistics) is intended for Spring Boot 2.x. Because the names and tag structure of HTTP metrics changed in Spring Boot 3.x, the HTTP request panels will show “No data” even after importing it. If you’re on 3.x, use 4701 or 17175 instead.
After importing, select the Prometheus data source you registered earlier, and metrics will start flowing in immediately.
Adding Custom Metrics
Simply injecting MeterRegistry lets you add business logic metrics with ease.
@Service
public class OrderService {
private final Counter orderCounter;
private final AtomicInteger pendingOrders;
public OrderService(MeterRegistry registry) {
this.orderCounter = Counter.builder("order.created.total")
.description("Total number of orders created")
.register(registry);
this.pendingOrders = new AtomicInteger(0);
Gauge.builder("order.pending", pendingOrders, AtomicInteger::get)
.description("Number of pending orders")
.register(registry);
}
public void createOrder(Order order) {
orderCounter.increment();
pendingOrders.incrementAndGet();
}
}
If you want to measure method execution time, the @Timed annotation is a convenient option.
import io.micrometer.core.annotation.Timed;
@Timed(value = "order.process.time", description = "Time taken to process order")
public void processOrder(Long orderId) {
// 注文処理
}
To use @Timed, registering a TimedAspect Bean is mandatory. Without it, @Timed is completely ignored.
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
Also note that self-invocation within the same class (calls that don’t go through the AOP proxy) will not be measured.
Verifying Custom Metrics in Grafana
You can check the metrics you added right away from Grafana’s Explore screen.
- Open Explore and search for
order_created_totalin the Metrics Browser - For Counters, the standard practice is to view the rate of increase with the
rate()function rather than the raw value
rate(order_created_total[5m])
[5m] means “the average rate of increase over 5 minutes.” As a rule of thumb, the time window should be at least 4 times the scrape_interval. Narrowing it to something like [1m] leaves too few samples and makes the graph unstable.
- Once the graph is drawn, click Add to dashboard in the top right to add the panel to a dashboard
- For the current value of a Gauge, the Stat or Gauge visualization is the easiest to read
Click Save dashboard and it’s ready to share with your team.
Security Configuration for Production
The Prometheus endpoint contains information such as memory usage and thread counts, so make sure not to expose it externally.
The simplest approach is separating the management port.
management:
server:
port: 8081
endpoints:
web:
exposure:
include: health, prometheus
A reliable setup is to dedicate port 8081 to the internal cluster network where Prometheus runs, and use a firewall to expose only 8080 (the application port) externally.
If you’ve already adopted Spring Security, you can also restrict access to Actuator endpoints to authenticated users using a SecurityFilterChain.
@Bean
public SecurityFilterChain actuatorSecurityFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/actuator/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.csrf(csrf -> csrf.disable());
return http.build();
}
Combining this with port separation gives you defense in depth. The fundamental rule is to list only the endpoints you need in exposure.include. Keep exposing everything with * limited to development environments.
Summary
Once you’ve built the Micrometer → Prometheus → Grafana pipeline, all that’s left is to grow your dashboards. Importing the official dashboards gets JVM metrics visible right away, and adding custom metrics takes nothing more than injecting MeterRegistry.
Combined with the Docker containerization guide, you can reproduce a production-grade containerized environment with monitoring locally. For log visualization, also check out the Logback and SLF4J guide.