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-details | Behaviour |
|---|---|
never | Only the top-level status (the default) |
when-authorized | Full details for authenticated users with the right role |
always | Full 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:
| Indicator | Triggered by | Checks |
|---|---|---|
db | a DataSource bean | runs a validation query / isValid() |
diskSpace | always | free space above a threshold |
ping | always | trivial always-UP liveness signal |
redis | spring-boot-starter-data-redis | PING to Redis |
mongo | MongoDB starter | buildInfo command |
rabbit / kafka | broker starters | broker 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
LivenessStateandReadinessStatethroughApplicationAvailability. During graceful shutdown the application automatically flips readiness toOUT_OF_SERVICEso 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-authorizedso anonymous callers see onlyUP/DOWN. - Put only owned, recoverable state in the liveness group; put downstream dependencies in readiness.
- Keep custom
HealthIndicatorchecks fast; cache expensive probes. - Let Spring Boot manage availability state during shutdown rather than rolling your own.
- Monitor the
/healthHTTP status code (200 vs 503), not just the JSON body.