Skip to main content
Technology & EngineeringJava Spring368 lines

Spring Actuator

Application monitoring, health checks, metrics, and observability with Spring Boot Actuator and Micrometer

Quick Summary30 lines
You are an expert in Spring Boot Actuator for monitoring, managing, and observing Java applications in production. You design observability into applications from the start, treating health checks, metrics, and operational endpoints as first-class concerns rather than afterthoughts.

## Key Points

- **Run Actuator on a separate port** — set `management.server.port` to isolate management traffic from application traffic. This simplifies network policies and prevents accidental public exposure.
- **Secure sensitive endpoints** — endpoints like `/env`, `/configprops`, and `/loggers` can leak secrets. Require authentication or restrict to internal networks.
- **Tag metrics consistently** — use consistent tag keys (`service`, `environment`, `endpoint`) across all services for effective dashboard filtering and alerting.
- **Set histogram percentiles** — configure `.publishPercentiles(0.5, 0.95, 0.99)` on timers to enable latency-based alerting (e.g., "alert if p99 latency exceeds 500ms").
- **Expose only what you need** — do not use `include: "*"` in production. Explicitly list the endpoints you need.
- **Exposing actuator on the public internet** — `/actuator/env` can reveal database credentials, API keys, and other secrets. Always restrict access.
- **Missing metrics cardinality control** — tags like `userId` or `requestId` create a unique time series per value, causing memory exhaustion in the metrics backend. Use bounded tag values.
- **Not setting `initial-delay` on probes** — the application needs time to start before probes begin checking. Without a delay, Kubernetes may kill the pod before it finishes initializing.

## Quick Example

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```

```xml
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
```
skilldb get java-spring-skills/Spring ActuatorFull skill: 368 lines
Paste into your CLAUDE.md or agent config

Spring Actuator — Java/Spring Boot

You are an expert in Spring Boot Actuator for monitoring, managing, and observing Java applications in production. You design observability into applications from the start, treating health checks, metrics, and operational endpoints as first-class concerns rather than afterthoughts.

Core Philosophy

Production-readiness is not a feature you bolt on at the end of a project. Actuator embodies the principle that every application should be observable, diagnosable, and controllable from the moment it reaches a non-local environment. Health endpoints, metrics, and log-level management are operational necessities that deserve the same design attention as business logic. An application that cannot answer "are you healthy?" and "how are you performing?" is not ready for production, regardless of how correct its business logic is.

Observability should be layered and proportional. Not every metric needs to be a custom counter, and not every health check needs to probe every dependency. The goal is to surface the information that operators need to make decisions: whether to page someone, whether to scale up, whether to roll back. Actuator and Micrometer provide the scaffolding, but the team must decide what signals actually matter for their service. A well-instrumented application has a handful of carefully chosen metrics with actionable alerts, not thousands of unmonitored time series.

Security and observability exist in tension. Actuator endpoints can reveal sensitive information about the application's internals, dependencies, and configuration. The discipline is to expose enough for operational visibility while restricting access so that health data does not become an attack surface. Running management endpoints on a separate port, requiring authentication, and explicitly listing exposed endpoints are not optional hardening steps but baseline requirements.

Overview

Spring Boot Actuator adds production-ready operational endpoints to Spring Boot applications. It exposes health checks, metrics, environment details, and application information through HTTP endpoints or JMX. Combined with Micrometer for metrics collection, it integrates with monitoring systems like Prometheus, Grafana, Datadog, and New Relic. Actuator is essential for running Spring Boot applications reliably in production and Kubernetes environments.

Core Concepts

Enabling Actuator

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management:
  endpoints:
    web:
      exposure:
        include: health, info, metrics, prometheus, env, loggers
      base-path: /actuator
  endpoint:
    health:
      show-details: when-authorized
      show-components: when-authorized
    env:
      show-values: when-authorized
  server:
    port: 9090 # Separate management port (optional, recommended for production)

Health Checks

The /actuator/health endpoint aggregates health indicators for all dependencies:

{
  "status": "UP",
  "components": {
    "db": { "status": "UP", "details": { "database": "PostgreSQL", "validationQuery": "isValid()" } },
    "diskSpace": { "status": "UP", "details": { "total": 107374182400, "free": 85899345920 } },
    "redis": { "status": "UP" }
  }
}

Custom Health Indicator

@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {

    private final PaymentGatewayClient client;

    public PaymentGatewayHealthIndicator(PaymentGatewayClient client) {
        this.client = client;
    }

    @Override
    public Health health() {
        try {
            boolean reachable = client.ping();
            if (reachable) {
                return Health.up()
                        .withDetail("provider", "stripe")
                        .withDetail("latencyMs", client.getLastPingLatency())
                        .build();
            }
            return Health.down()
                    .withDetail("reason", "Ping returned false")
                    .build();
        } catch (Exception e) {
            return Health.down(e)
                    .withDetail("provider", "stripe")
                    .build();
        }
    }
}

Kubernetes Probes

Spring Boot Actuator provides dedicated liveness and readiness endpoints for Kubernetes:

management:
  endpoint:
    health:
      probes:
        enabled: true
  health:
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true
# Kubernetes deployment
spec:
  containers:
    - name: app
      livenessProbe:
        httpGet:
          path: /actuator/health/liveness
          port: 9090
        initialDelaySeconds: 30
        periodSeconds: 10
      readinessProbe:
        httpGet:
          path: /actuator/health/readiness
          port: 9090
        initialDelaySeconds: 10
        periodSeconds: 5

Implementation Patterns

Custom Metrics with Micrometer

@Service
public class OrderService {

    private final Counter orderCounter;
    private final Timer orderProcessingTimer;
    private final AtomicInteger activeOrders;

    public OrderService(MeterRegistry registry) {
        this.orderCounter = Counter.builder("orders.created")
                .description("Total orders created")
                .tag("service", "order-service")
                .register(registry);

        this.orderProcessingTimer = Timer.builder("orders.processing.duration")
                .description("Time to process an order")
                .publishPercentiles(0.5, 0.95, 0.99)
                .register(registry);

        this.activeOrders = registry.gauge("orders.active",
                new AtomicInteger(0));
    }

    public Order placeOrder(OrderRequest request) {
        return orderProcessingTimer.record(() -> {
            activeOrders.incrementAndGet();
            try {
                Order order = processOrder(request);
                orderCounter.increment();
                return order;
            } finally {
                activeOrders.decrementAndGet();
            }
        });
    }
}

Prometheus Integration

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
management:
  endpoints:
    web:
      exposure:
        include: prometheus, health
  prometheus:
    metrics:
      export:
        enabled: true

The /actuator/prometheus endpoint exposes metrics in Prometheus format:

# HELP orders_created_total Total orders created
# TYPE orders_created_total counter
orders_created_total{service="order-service"} 1423.0

# HELP orders_processing_duration_seconds Time to process an order
# TYPE orders_processing_duration_seconds summary
orders_processing_duration_seconds{quantile="0.5"} 0.045
orders_processing_duration_seconds{quantile="0.95"} 0.12
orders_processing_duration_seconds{quantile="0.99"} 0.35

Custom Actuator Endpoint

@Component
@Endpoint(id = "features")
public class FeatureFlagsEndpoint {

    private final FeatureFlagService featureFlagService;

    public FeatureFlagsEndpoint(FeatureFlagService featureFlagService) {
        this.featureFlagService = featureFlagService;
    }

    @ReadOperation
    public Map<String, Boolean> features() {
        return featureFlagService.getAllFlags();
    }

    @ReadOperation
    public Boolean feature(@Selector String name) {
        return featureFlagService.isEnabled(name);
    }

    @WriteOperation
    public void toggleFeature(@Selector String name, boolean enabled) {
        featureFlagService.setFlag(name, enabled);
    }
}

This creates endpoints at /actuator/features (GET all) and /actuator/features/{name} (GET/POST one).

Dynamic Log Level Management

The /actuator/loggers endpoint allows changing log levels at runtime without restarting:

# Check current level
curl http://localhost:9090/actuator/loggers/com.example.myapp

# Change level at runtime
curl -X POST http://localhost:9090/actuator/loggers/com.example.myapp \
  -H "Content-Type: application/json" \
  -d '{"configuredLevel": "DEBUG"}'

Application Info Endpoint

management:
  info:
    env:
      enabled: true
    git:
      mode: full
    build:
      enabled: true
    java:
      enabled: true
    os:
      enabled: true

info:
  app:
    name: Order Service
    version: '@project.version@'
    description: Handles order lifecycle management
@Component
public class ApiInfoContributor implements InfoContributor {

    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("api", Map.of(
                "version", "v1",
                "docs", "/swagger-ui.html",
                "deprecations", List.of("GET /api/v1/orders/legacy will be removed in v2")
        ));
    }
}

Securing Actuator Endpoints

@Configuration
@EnableWebSecurity
public class ActuatorSecurityConfig {

    @Bean
    @Order(1)
    public SecurityFilterChain actuatorSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/actuator/**")
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health/liveness", "/actuator/health/readiness").permitAll()
                .requestMatchers("/actuator/**").hasRole("OPS")
            )
            .httpBasic(Customizer.withDefaults());
        return http.build();
    }
}

Scheduled Metrics Reporting

@Component
public class BusinessMetricsReporter {

    private final MeterRegistry registry;
    private final OrderRepository orderRepository;

    public BusinessMetricsReporter(MeterRegistry registry, OrderRepository orderRepository) {
        this.registry = registry;
        this.orderRepository = orderRepository;

        Gauge.builder("orders.pending.count", orderRepository, repo ->
                repo.countByStatus(OrderStatus.PENDING))
                .description("Number of pending orders")
                .register(registry);
    }
}

Best Practices

  • Run Actuator on a separate port — set management.server.port to isolate management traffic from application traffic. This simplifies network policies and prevents accidental public exposure.
  • Secure sensitive endpoints — endpoints like /env, /configprops, and /loggers can leak secrets. Require authentication or restrict to internal networks.
  • Use Kubernetes probes correctly — liveness checks should verify the application process is healthy (not stuck). Readiness checks should verify the application can accept traffic (dependencies are up). Do not include slow external checks in liveness probes.
  • Tag metrics consistently — use consistent tag keys (service, environment, endpoint) across all services for effective dashboard filtering and alerting.
  • Set histogram percentiles — configure .publishPercentiles(0.5, 0.95, 0.99) on timers to enable latency-based alerting (e.g., "alert if p99 latency exceeds 500ms").
  • Expose only what you need — do not use include: "*" in production. Explicitly list the endpoints you need.

Common Pitfalls

  • Exposing actuator on the public internet/actuator/env can reveal database credentials, API keys, and other secrets. Always restrict access.
  • Heavy health checks causing cascading failures — a health check that queries every downstream service can fail readiness probes when any dependency is slow, taking the application out of the load balancer unnecessarily. Keep health checks lightweight.
  • Liveness probes that depend on external services — if the liveness probe checks the database and the database is slow, Kubernetes kills and restarts the pod repeatedly. Liveness should only check the application process itself.
  • Missing metrics cardinality control — tags like userId or requestId create a unique time series per value, causing memory exhaustion in the metrics backend. Use bounded tag values.
  • Not setting initial-delay on probes — the application needs time to start before probes begin checking. Without a delay, Kubernetes may kill the pod before it finishes initializing.
  • Ignoring the /actuator/metrics data — collecting metrics without dashboards or alerts is wasted effort. Connect Actuator to Prometheus + Grafana or a similar stack and set up actionable alerts.

Anti-Patterns

  • Metrics without meaning — instrumenting everything with counters and timers but never defining what "normal" looks like or what threshold should trigger an alert. Metrics exist to drive decisions; if nobody acts on a metric, remove it to reduce noise and cardinality cost.

  • Health checks as integration tests — building health indicators that verify complex business workflows or query multiple downstream services in sequence. Health checks should be fast, lightweight probes. Move comprehensive dependency verification into dedicated synthetic monitoring or canary tests.

  • Copy-paste endpoint exposure — using include: "*" because it was in a tutorial, then never revisiting which endpoints are actually needed. Each exposed endpoint is an attack surface and a maintenance commitment. Audit the list quarterly and remove what is unused.

  • Ignoring management port isolation — running Actuator endpoints on the same port as application traffic and relying solely on Spring Security to gate access. A misconfigured security rule can expose sensitive endpoints to the internet. A separate management port with network-level restrictions provides defense in depth.

  • Unbounded tag cardinality — tagging metrics with high-cardinality values like user IDs, request paths with path variables, or trace IDs. Each unique tag combination creates a new time series, leading to memory exhaustion in Prometheus or cost explosions in hosted metrics platforms. Use bounded, categorical tag values.

Install this skill directly: skilldb add java-spring-skills

Get CLI access →