Spring Boot Actuator in Production: Custom Health Checks, Metrics & Security Hardening
Series: Java Performance Engineering Series
Why Actuator Matters in Production
Ever deployed a microservice only to realize you're flying blind? No visibility into JVM health, no way to check if database connections are healthy, no metrics to debug that mysterious latency spike at 2 AM. That's the black-box service problem, and Spring Boot Actuator solves it.
Actuator exposes production-ready endpoints for monitoring and managing your application. But here's the catch: most teams enable it with defaults, exposing /actuator/env to the internet and wondering why they got breached. Or they write custom health checks that block the entire liveness probe thread.
This guide shows you how we use Actuator in production systems serving 10M+ requests/day—custom health indicators, secure endpoint exposure, Micrometer metrics, and Kubernetes integration.
Actuator Architecture: Endpoints, InfoContributor, HealthIndicator
Actuator is built on three core abstractions:
- Endpoints — Expose application internals (health, metrics, beans, env)
- HealthIndicator — Contribute to
/actuator/healthwith custom checks - InfoContributor — Add metadata to
/actuator/info
Enable Actuator in pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
By default, only /health and /info are exposed over HTTP. Enable more in application.yml:
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: when-authorized
Building Custom Health Indicators
Spring Boot auto-configures health checks for DataSource, Redis, Kafka, etc. But what about your critical external API dependency?
@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {
@Autowired
private RestTemplate restTemplate;
@Override
public Health health() {
try {
// Call external service health endpoint with timeout
ResponseEntity<String> response = restTemplate.exchange(
"https://payment-api.example.com/health",
HttpMethod.GET,
null,
String.class
);
if (response.getStatusCode() == HttpStatus.OK) {
return Health.up()
.withDetail("gateway", "payment-api")
.withDetail("latency", "45ms")
.build();
}
return Health.down()
.withDetail("reason", "Non-200 status: " + response.getStatusCode())
.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.withException(e)
.build();
}
}
}
Health Check Timeout Trap
Kubernetes liveness probes have a 1-second default timeout. If your health check calls slow external APIs, use @Async or cache results. Never block the health endpoint.
Micrometer Metrics: Counters, Gauges, and Timers
Actuator uses Micrometer—a dimensional metrics facade that works with Prometheus, Datadog, CloudWatch, etc.
Counter example — Increment on every payment attempt:
@Service
public class PaymentService {
private final Counter paymentCounter;
public PaymentService(MeterRegistry registry) {
this.paymentCounter = Counter.builder("payments.attempted")
.tag("service", "checkout")
.description("Total payment attempts")
.register(registry);
}
public void processPayment(Payment payment) {
paymentCounter.increment();
// business logic
}
}
Timer example — Measure external API call duration:
@Service
public class ExternalApiClient {
private final Timer apiTimer;
public ExternalApiClient(MeterRegistry registry) {
this.apiTimer = Timer.builder("external.api.calls")
.tag("api", "payment-gateway")
.description("Payment gateway API call duration")
.register(registry);
}
public Response callApi() {
return apiTimer.record(() -> {
return restTemplate.getForObject(...);
});
}
}
Securing Actuator Endpoints
Default configuration exposes sensitive endpoints to the internet. Secure them with Spring Security:
@Configuration
public class ActuatorSecurityConfig {
@Bean
public SecurityFilterChain actuatorSecurity(HttpSecurity http) throws Exception {
http
.securityMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests(auth -> auth
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
.anyRequest().hasRole("ACTUATOR_ADMIN")
)
.httpBasic();
return http.build();
}
}
Expose only health and info publicly. Everything else (env, beans, heapdump) requires authentication.
Kubernetes Liveness/Readiness Probe Integration
Spring Boot 2.3+ provides dedicated liveness and readiness states:
management:
endpoint:
health:
probes:
enabled: true
health:
livenessState:
enabled: true
readinessState:
enabled: true
Kubernetes deployment:
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
Production Debugging with /threaddump and /heapdump
When your app is hanging in production, use /actuator/threaddump to see what threads are blocked:
curl -u admin:secret https://api.example.com/actuator/threaddump > threaddump.json
For memory leaks, trigger a heap dump:
curl -u admin:secret -X POST https://api.example.com/actuator/heapdump -o heapdump.hprof
Analyze with VisualVM or Eclipse MAT.
Failure Scenarios and Troubleshooting
Scenario 1: Pod restarting every 30 seconds — Liveness probe fails because health check calls slow DB query. Fix: Move DB check to readiness only.
Scenario 2: Metrics endpoint returns 404 — Forgot to add micrometer-registry-prometheus dependency. Fix: Add the correct registry dependency.
Scenario 3: Health endpoint exposed to internet — Security misconfiguration. Fix: Use Spring Security to restrict access.
Key Takeaways
- Use custom HealthIndicators for critical external dependencies
- Secure sensitive endpoints — only expose health/info publicly
- Separate liveness and readiness — liveness = "is the app alive?", readiness = "can it serve traffic?"
- Instrument business metrics with Micrometer counters and timers
- Set timeouts on health checks — never block Kubernetes probes
Conclusion
Spring Boot Actuator transforms your microservices from black boxes into observable systems. Combined with proper security, custom health checks, and Kubernetes integration, you get production-grade monitoring out of the box.
Next, dive deeper into performance tuning, JVM profiling, and distributed tracing to complete your observability stack.