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

EndpointMethodPurpose
/actuator/healthGETApp health status
/actuator/health/livenessGETK8s liveness probe
/actuator/health/readinessGETK8s readiness probe
/actuator/metricsGETList all metrics
/actuator/metrics/{name}GETSpecific metric values
/actuator/prometheusGETPrometheus scrape
/actuator/infoGETApp info
/actuator/loggersGET/POSTDynamic log level
/actuator/envGETEnvironment properties
/actuator/threaddumpGETJVM thread dump