Actuator & Observability
Health checks, metrics, Prometheus integration, custom endpoints
Spring Boot Actuator exposes production-ready endpoints for monitoring, health checking, and managing a running Spring Boot application. It integrates natively with Prometheus, Grafana, and cloud health-check systems — essential infrastructure for any production deployment.
Key Points
- Include actuator: spring-boot-starter-actuator — endpoints at /actuator/* by default
- /actuator/health: UP/DOWN status — includes sub-indicators for DB, Redis, disk, and custom components
- /actuator/metrics: Micrometer metric names — /actuator/metrics/jvm.memory.used?tag=area:heap for specific metrics
- /actuator/info: application version, git commit, custom info — expose with management.info.git.mode=full
- /actuator/env: all configuration properties — sensitive values masked by default
- /actuator/loggers: view and change log levels at runtime without restart — POST {"configuredLevel": "DEBUG"}
- /actuator/threaddump and /actuator/heapdump: diagnostic dumps — use with JFR/async-profiler for production analysis
- Prometheus integration: add io.micrometer:micrometer-registry-prometheus → /actuator/prometheus exposes scrape endpoint
- Security: expose only health + info publicly; lock /actuator with Spring Security or move to management port
Spring Actuator: YAML config with Prometheus, custom HealthIndicator, Micrometer Counter + Timer, Kubernetes probe integration
# application.yml — actuator config
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,loggers
endpoint:
health:
show-details: when-authorized # only for authenticated users
probes:
enabled: true # /actuator/health/liveness + /health/readiness
health:
db:
enabled: true
redis:
enabled: true
metrics:
tags:
application: ${spring.application.name} # add app label to all metrics
info:
git:
mode: full
---
// Custom health indicator
@Component
public class ExternalApiHealthIndicator implements HealthIndicator {
private final PaymentClient client;
@Override
public Health health() {
try {
client.ping();
return Health.up().withDetail("payment-api", "reachable").build();
} catch (Exception e) {
return Health.down().withDetail("reason", e.getMessage()).build();
}
}
}
// Custom metric with Micrometer
@Service
public class OrderService {
private final Counter ordersPlaced;
private final Timer orderProcessingTime;
public OrderService(MeterRegistry registry) {
this.ordersPlaced = Counter.builder("orders.placed")
.description("Total orders placed")
.tag("channel", "web")
.register(registry);
this.orderProcessingTime = Timer.builder("orders.processing.time")
.description("Order processing duration")
.register(registry);
}
public Order place(OrderRequest req) {
ordersPlaced.increment();
return orderProcessingTime.record(() -> processOrder(req));
}
}
// Kubernetes probes using actuator
# liveness: /actuator/health/liveness → kills pod if DOWN
# readiness: /actuator/health/readiness → removes pod from load balancerReal-World Example
The liveness and readiness probes (management.endpoint.health.probes.enabled=true) map directly to Kubernetes livenessProbe and readinessProbe. Liveness DOWN → k8s restarts the pod. Readiness DOWN → k8s removes the pod from the Service load balancer but does not restart it. This distinction matters during startup (readiness DOWN while warming up) and graceful shutdown (readiness DOWN immediately, liveness stays UP).