Liveness vs readiness precisely, Spring Boot Actuator health groups and custom HealthIndicators, and whether a dependency outage should make a service unready or degrade gracefully.
Published September 23, 2026
A liveness check answers one narrow question: is this process fundamentally alive and responsive, or has it entered a broken state (deadlocked, hung) that only a restart can fix? A liveness check failing tells the orchestrator (Kubernetes, most commonly) to kill and restart the instance — it should be a minimal check (is the JVM even responding to a basic ping), not one that depends on external systems, since a liveness check failing due to a downstream dependency being down would cause the orchestrator to needlessly restart a perfectly healthy process, achieving nothing.
A readiness check answers a different question: is this specific instance ready to receive new traffic right now? A readiness check failing tells the orchestrator/load balancer to remove this instance from rotation temporarily (not restart it) — appropriate during startup (before all beans/connections are initialized), during a graceful shutdown drain, or when a critical dependency is genuinely unreachable and this instance can't usefully serve requests at the moment.
This liveness/readiness distinction is exactly the gap in Why Microservices Fail's "health checks report green but customers report outages" scenario — a shallow check that only implements liveness (process is alive) tells you nothing about whether the instance can actually serve real requests; readiness is the check that's supposed to answer that, and its absence (or shallowness) is precisely the common root cause.
@Component
class DatabaseHealthIndicator implements HealthIndicator {
public Health health() {
try {
jdbcTemplate.execute("SELECT 1"); // a real, minimal query against the actual dependency
return Health.up().build();
} catch (Exception e) {
return Health.down(e).build();
}
}
}
management:
endpoint:
health:
group:
liveness:
include: livenessState
readiness:
include: readinessState, db, diskSpace # readiness ADDITIONALLY checks real dependencies
Spring Boot Actuator supports health groups, letting /actuator/health/liveness and /actuator/health/readiness expose genuinely different checks — liveness stays minimal (just livenessState), readiness includes custom HealthIndicator implementations (like the database check above) that actually exercise the dependency paths real traffic needs. A HealthIndicator is the extension point for adding a check for any dependency (a message broker connection, an external API, disk space) that matters for whether this instance can genuinely serve traffic.
This is a genuine, case-by-case design decision, not a fixed rule:
Naming this distinction explicitly per dependency (not a blanket policy for every dependency a service has) is the mature answer — some dependencies genuinely warrant unready, others clearly warrant graceful degradation, and treating every dependency identically in either direction is a real design miss.
Q: What happens if a liveness check DOES depend on an external system and that system goes down? A: Every instance would simultaneously report unhealthy and get restarted by the orchestrator — potentially in a loop, if the external system stays down and the restarted instances immediately fail liveness again — a real, damaging failure mode, which is exactly why liveness checks are deliberately kept minimal and dependency-free, restricted purely to 'is this process itself functioning.'
Q: How does readiness interact with graceful shutdown? A: A service receiving a shutdown signal should immediately flip readiness to false (removing itself from the load balancer's rotation) BEFORE actually stopping — this drains in-flight traffic away first, avoiding requests being routed to an instance that's about to terminate, a standard and important part of a zero-downtime deployment process.
Q: Could a HealthIndicator itself become a performance problem if checked too frequently? A: Yes — a readiness check that runs an expensive query against a database on every single orchestrator health-check poll (which might happen every few seconds) adds real, avoidable load; a common mitigation is caching the health-check result for a short interval (a few seconds) rather than re-executing the full dependency check on every single poll.
Q: Is 'degrade gracefully' always the safer choice, given it avoids taking an instance out of rotation? A: No — degrading gracefully when a dependency is actually ESSENTIAL means serving requests that will fail anyway (or worse, produce silently wrong results), which is worse than honestly reporting unready and letting traffic route elsewhere; the choice needs to match whether the dependency is genuinely optional for that specific request path, not default to whichever choice looks less disruptive.