Spring Boot Actuator production health checks Micrometer metrics security
Core Java March 22, 2026 16 min read

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/health with 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.

Related Articles

Leave a Comment