Spring Boot Actuator Guide
Production-ready monitoring with Spring Boot Actuator: health checks, metrics, custom indicators, and Prometheus/Grafana integration.
1. Configuration (application.yml)
management:
endpoint:
health:
show-details: when-authorized # never | always | when-authorized
probes:
enabled: true # /health/liveness, /health/readiness
shutdown:
enabled: false
endpoints:
web:
base-path: /actuator
exposure:
include: health,info,metrics,prometheus,loggers
# exclude sensitive endpoints from public
metrics:
tags:
application: ${spring.application.name}
environment: ${spring.profiles.active:default}
distribution:
percentiles-histogram:
http.server.requests: true
percentiles:
http.server.requests: 0.5, 0.95, 0.99
# application info shown at /actuator/info
info:
app:
name: "@project.name@"
version: "@project.version@"
description: "@project.description@"
2. Custom Health Indicator
import org.springframework.boot.actuate.health.*;
@Component
public class ExternalApiHealthIndicator implements HealthIndicator {
private final RestTemplate restTemplate;
public ExternalApiHealthIndicator(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public Health health() {
try {
ResponseEntity<String> resp = restTemplate.getForEntity(
"https://api.external.com/ping", String.class);
if (resp.getStatusCode().is2xxSuccessful()) {
return Health.up()
.withDetail("url", "https://api.external.com")
.withDetail("status", resp.getStatusCode().value())
.build();
}
return Health.down().withDetail("status", resp.getStatusCode().value()).build();
} catch (Exception e) {
return Health.down(e).withDetail("error", e.getMessage()).build();
}
}
}
// Composite health โ groups multiple indicators
@Component("database")
public class DatabaseHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
// check DB connection
builder.up().withDetail("pool", "active=5, idle=5, max=10");
}
}
3. Custom Metrics with Micrometer
import io.micrometer.core.instrument.*;
@Service
public class ArticleService {
private final Counter publishedCounter;
private final Timer processTimer;
private final AtomicInteger pendingGauge;
public ArticleService(MeterRegistry registry) {
this.publishedCounter = Counter.builder("articles.published")
.description("Total articles published")
.tag("type", "blog")
.register(registry);
this.processTimer = Timer.builder("articles.processing.time")
.description("Article processing duration")
.publishPercentiles(0.5, 0.95, 0.99)
.register(registry);
this.pendingGauge = registry.gauge("articles.pending",
new AtomicInteger(0));
}
public ArticleDto publish(Long id) {
return processTimer.record(() -> {
Article article = repo.findById(id).orElseThrow();
article.setStatus(ArticleStatus.PUBLISHED);
repo.save(article);
publishedCounter.increment();
return ArticleDto.from(article);
});
}
}
4. Prometheus + Grafana Setup
// pom.xml / build.gradle
// spring-boot-starter-actuator + micrometer-registry-prometheus
// application.yml
management:
endpoints:
web:
exposure:
include: prometheus,health
metrics:
export:
prometheus:
enabled: true
// prometheus.yml โ scrape config
scrape_configs:
- job_name: 'spring-app'
scrape_interval: 15s
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['localhost:8080']
// Useful Prometheus queries (PromQL)
// Request rate: rate(http_server_requests_seconds_count[1m])
// Error rate: rate(http_server_requests_seconds_count{status=~"5.."}[5m])
// P95 latency: histogram_quantile(0.95, http_server_requests_seconds_bucket)
5. Liveness & Readiness Probes
// Kubernetes probes
// /actuator/health/liveness โ is app running? restart if DOWN
// /actuator/health/readiness โ is app ready for traffic? remove from LB if DOWN
// application.yml
management:
endpoint:
health:
probes:
enabled: true
// Custom readiness state โ e.g., during warm-up
@Component
public class WarmUpReadinessCheck implements ApplicationListener<ApplicationReadyEvent> {
private final ApplicationContext context;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
AvailabilityChangeEvent.publish(event.getApplicationContext(),
ReadinessState.ACCEPTING_TRAFFIC);
}
}
// Programmatically mark as not ready
@Autowired
private ApplicationEventPublisher eventPublisher;
public void setNotReady() {
AvailabilityChangeEvent.publish(eventPublisher, this, ReadinessState.REFUSING_TRAFFIC);
}
6. Key Actuator Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
| /actuator/health | GET | App health status |
| /actuator/health/liveness | GET | K8s liveness probe |
| /actuator/health/readiness | GET | K8s readiness probe |
| /actuator/metrics | GET | List all metrics |
| /actuator/metrics/{name} | GET | Specific metric values |
| /actuator/prometheus | GET | Prometheus scrape |
| /actuator/info | GET | App info |
| /actuator/loggers | GET/POST | Dynamic log level |
| /actuator/env | GET | Environment properties |
| /actuator/threaddump | GET | JVM thread dump |