Course topics

By WebNest Studio

Spring Boot Tutorial

Health Checks, Readiness and Liveness Probes

Load balancers and orchestrators like Kubernetes constantly ask your application two different questions. Liveness: "Is this process broken beyond repair? Should I restart it?" Readiness: "Can this instance handle traffic right now? Should I send it requests?" Answering these correctly is what makes rolling deployments, autoscaling and self-healing work without dropped requests.

This lesson covers Actuator's health endpoint and indicators, custom health checks, liveness and readiness state, health groups, how Spring Boot exposes Kubernetes probes, and how graceful shutdown fits in.

The Health Endpoint

/actuator/health aggregates HealthIndicators: Spring Boot adds indicators for the database, disk space, Redis, MongoDB, RabbitMQ, Kafka, mail server and more, depending on your dependencies. The overall status is UP only if all indicators are UP. Details are hidden by default; show them to authorised users with management.endpoint.health.show-details=when-authorized.

Liveness vs Readiness

These must not be confused. Liveness should reflect only the application's internal state — a deadlock or a corrupted state that a restart would fix. It must not include the database: if the database is down, restarting every pod will not help and causes a restart storm. Readiness says whether the instance should receive traffic: false during startup, while warming caches, during shutdown, or when a critical dependency is unavailable.

Probes in Spring Boot

Spring Boot exposes /actuator/health/liveness and /actuator/health/readiness health groups, automatically on Kubernetes or with management.endpoint.health.probes.enabled=true. Its ApplicationAvailability tracks LivenessState and ReadinessState: readiness becomes ACCEPTING_TRAFFIC only after runners have completed, and switches to REFUSING_TRAFFIC at the start of graceful shutdown. You can add indicators to a group or change state yourself by publishing an AvailabilityChangeEvent.

Custom Health Indicators

Implement HealthIndicator (or AbstractHealthIndicator) to report the state of a dependency Spring Boot does not know about, such as a partner API or a licence server. Keep checks fast and cached — probes run every few seconds on every instance.

Examples

Configuration and endpoint output

Java
# application.yml
management:
  endpoint:
    health:
      show-details: when-authorized
      probes:
        enabled: true
      group:
        readiness:
          include: readinessState, db, redis      # ready only if DB and Redis are reachable
  endpoints:
    web:
      exposure:
        include: health
Output
GET /actuator/health/liveness  -> 200 {"status":"UP"}
GET /actuator/health/readiness -> 200 {"status":"UP"}
(database stopped)
GET /actuator/health/readiness -> 503 {"status":"OUT_OF_SERVICE"}
GET /actuator/health/liveness  -> 200 {"status":"UP"}      (no pointless restart)

A custom health indicator for a partner API

Java
@Component("paymentGateway")
public class PaymentGatewayHealthIndicator implements HealthIndicator {

    private final RestClient client;

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

    @Override
    public Health health() {
        try {
            ResponseEntity<Void> r = client.get().uri("/health").retrieve().toBodilessEntity();
            return Health.up().withDetail("status", r.getStatusCode().value()).build();
        } catch (Exception e) {
            return Health.down(e).withDetail("endpoint", "https://pay.example.com/health").build();
        }
    }
}
Output
GET /actuator/health (as admin)
{"status":"DOWN","components":{"db":{"status":"UP"},"diskSpace":{"status":"UP"},
 "paymentGateway":{"status":"DOWN","details":{"endpoint":"https://pay.example.com/health","error":"...Connection refused"}}}}

Refusing traffic while a cache warms up

Java
@Component
public class CacheWarmer {

    private final ApplicationEventPublisher events;
    private final CatalogCache cache;

    public CacheWarmer(ApplicationEventPublisher events, CatalogCache cache) {
        this.events = events;
        this.cache = cache;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void warm() {
        AvailabilityChangeEvent.publish(events, this, ReadinessState.REFUSING_TRAFFIC);
        cache.loadAll();                                   // takes ~20 s
        AvailabilityChangeEvent.publish(events, this, ReadinessState.ACCEPTING_TRAFFIC);
    }
}
Output
0-20 s after start: /actuator/health/readiness -> 503 (Kubernetes sends no traffic)
after warm-up:      /actuator/health/readiness -> 200

Common Mistakes

  • Including the database in the liveness probe, causing all pods to restart during a database outage.
  • Pointing Kubernetes probes at /actuator/health instead of the separate liveness and readiness groups.
  • Writing slow health checks that call expensive APIs on every probe.
  • Showing full health details publicly, revealing infrastructure information.
  • Ignoring readiness during startup, so traffic arrives before caches and connections are ready.

Key Points to Remember

  • /actuator/health aggregates HealthIndicators for your dependencies.
  • Liveness = restart if broken (internal state only); readiness = send traffic or not.
  • Spring Boot exposes /actuator/health/liveness and /readiness groups for Kubernetes.
  • Custom HealthIndicators report dependencies Spring Boot does not know.
  • Publish AvailabilityChangeEvent to control readiness, e.g. during warm-up.

Practice the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.