Skip to content
Spring Boot sb production 4 min read

Health Checks

A health check tells the outside world whether your application is able to serve traffic. Spring Boot Actuator aggregates the status of every dependency — database, disk, message broker, cache — into a single /actuator/health endpoint that load balancers and orchestrators poll. When something breaks, the endpoint flips to DOWN, and your platform can stop routing traffic or restart the pod.

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

The health endpoint

Out of the box, GET /actuator/health returns just an overall status. The detail is hidden unless you opt in, because component details can reveal infrastructure.

management:
  endpoints:
    web:
      exposure:
        include: health
  endpoint:
    health:
      show-details: when-authorized   # never | when-authorized | always
      show-components: when-authorized
show-detailsBehaviour
neverOnly the top-level status (the default)
when-authorizedFull details for authenticated users with the right role
alwaysFull details for everyone (use only behind a firewall)

With details enabled, the response composes every registered indicator:

curl http://localhost:8080/actuator/health

Output:

{
  "status": "UP",
  "components": {
    "db": { "status": "UP", "details": { "database": "PostgreSQL", "validationQuery": "isValid()" } },
    "diskSpace": { "status": "UP", "details": { "total": 250790436864, "free": 91234508800, "threshold": 10485760, "exists": true } },
    "ping": { "status": "UP" },
    "redis": { "status": "UP", "details": { "version": "7.2.4" } }
  }
}

The aggregate status is the worst of all components: any DOWN makes the whole endpoint DOWN and returns HTTP 503. A healthy endpoint returns 200.

Built-in indicators

Spring Boot auto-registers indicators based on what is on the classpath. The most common:

IndicatorTriggered byChecks
dba DataSource beanruns a validation query / isValid()
diskSpacealwaysfree space above a threshold
pingalwaystrivial always-UP liveness signal
redisspring-boot-starter-data-redisPING to Redis
mongoMongoDB starterbuildInfo command
rabbit / kafkabroker startersbroker connectivity

You can disable a specific indicator if a dependency is optional:

management:
  health:
    redis:
      enabled: false
    diskspace:
      threshold: 50MB   # mark DOWN when free space drops below 50MB

Liveness and readiness for Kubernetes

Kubernetes distinguishes two probe types, and Spring Boot maps them to health groups automatically when it detects it is running in Kubernetes (or you can force them on).

  • Liveness — “is the app alive?” A failed liveness probe restarts the pod. It should fail only on unrecoverable state, never on a transient downstream outage.
  • Readiness — “can the app accept traffic?” A failed readiness probe removes the pod from the load balancer but does not restart it.
management:
  endpoint:
    health:
      probes:
        enabled: true        # forces the groups even outside Kubernetes
      group:
        readiness:
          include: readinessState, db, redis
        liveness:
          include: livenessState

This creates two child endpoints:

curl http://localhost:8080/actuator/health/liveness
curl http://localhost:8080/actuator/health/readiness

Output:

{ "status": "UP" }

A matching pod spec wires the probes to these paths:

livenessProbe:
  httpGet: { path: /actuator/health/liveness, port: 8080 }
readinessProbe:
  httpGet: { path: /actuator/health/readiness, port: 8080 }

Note: Spring Boot manages LivenessState and ReadinessState through ApplicationAvailability. During graceful shutdown the application automatically flips readiness to OUT_OF_SERVICE so Kubernetes stops sending requests while in-flight ones drain.

Custom HealthIndicator

Implement HealthIndicator to surface the health of anything Spring does not know about — a third-party API, a license server, a feature flag service. The bean name (minus the HealthIndicator suffix) becomes the component key.

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;

@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {

    private final RestClient client;

    public PaymentGatewayHealthIndicator(RestClient.Builder builder) {
        this.client = builder.baseUrl("https://payments.example.com").build();
    }

    @Override
    public Health health() {
        try {
            String body = client.get().uri("/ping").retrieve().body(String.class);
            return Health.up().withDetail("response", body).build();
        } catch (Exception ex) {
            return Health.down(ex).withDetail("endpoint", "/ping").build();
        }
    }
}

This adds a paymentGateway component to the aggregate:

Output (gateway unreachable):

{
  "status": "DOWN",
  "components": {
    "db": { "status": "UP" },
    "paymentGateway": {
      "status": "DOWN",
      "details": { "endpoint": "/ping", "error": "java.net.ConnectException: Connection refused" }
    }
  }
}

Tip: Keep custom indicators fast and side-effect free — they run on every probe (often every few seconds). For slow checks, cache the result or exclude the indicator from the liveness group so a sluggish downstream never triggers a pod restart.

Warning: Don’t include a flaky external dependency in the liveness group. If it blips, Kubernetes will restart a perfectly healthy pod, turning a downstream hiccup into an outage.

Best Practices

  • Set show-details: when-authorized so anonymous callers see only UP/DOWN.
  • Put only owned, recoverable state in the liveness group; put downstream dependencies in readiness.
  • Keep custom HealthIndicator checks fast; cache expensive probes.
  • Let Spring Boot manage availability state during shutdown rather than rolling your own.
  • Monitor the /health HTTP status code (200 vs 503), not just the JSON body.
Last updated June 13, 2026
Was this helpful?