Chapter 4: Health Checks
Introduction
Health Checks allow you to verify if your application and its dependencies are functioning correctly. Essential for load balancers, Kubernetes, and monitoring.
🎯 Chapter Objectives
By the end of this chapter, you will know how to:
- ✅ Understand native health indicators
- ✅ Create custom health checks
- ✅ Configure health groups
- ✅ Implement liveness and readiness probes
1. Basic Health Check
Configuration
# Show details
management.endpoint.health.show-details=always
# Options: never, when_authorized, always
Response
curl http://localhost:8080/actuator/health
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "H2",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 499963174912,
"free": 234567891234,
"threshold": 10485760,
"path": "/app/.",
"exists": true
}
},
"ping": {
"status": "UP"
}
}
}
Possible Statuses
| Status | Description | HTTP Code |
|---|---|---|
UP | Everything is working | 200 |
DOWN | Something is not working | 503 |
OUT_OF_SERVICE | Out of service (maintenance) | 503 |
UNKNOWN | Unknown state | 200 |
2. Native Health Indicators
Spring Boot includes automatic health indicators:
| Indicator | Detected if... |
|---|---|
DataSourceHealthIndicator | DataSource configured |
DiskSpaceHealthIndicator | Always active |
MongoHealthIndicator | MongoDB configured |
RedisHealthIndicator | Redis configured |
RabbitHealthIndicator | RabbitMQ configured |
ElasticsearchHealthIndicator | Elasticsearch configured |
MailHealthIndicator | Mail configured |
Disable an Indicator
# Disable disk space check
management.health.diskspace.enabled=false
# Disable DB check
management.health.db.enabled=false
3. Custom Health Check
Simple Implementation
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class ExternalApiHealthIndicator implements HealthIndicator {
private final ExternalApiClient apiClient;
public ExternalApiHealthIndicator(ExternalApiClient apiClient) {
this.apiClient = apiClient;
}
@Override
public Health health() {
try {
boolean isUp = apiClient.ping();
if (isUp) {
return Health.up()
.withDetail("api", "external-api")
.withDetail("status", "reachable")
.build();
} else {
return Health.down()
.withDetail("api", "external-api")
.withDetail("status", "unreachable")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("api", "external-api")
.withDetail("error", e.getMessage())
.build();
}
}
}
Result
{
"status": "UP",
"components": {
"externalApi": {
"status": "UP",
"details": {
"api": "external-api",
"status": "reachable"
}
}
}
}
Health Check with Thresholds
@Component
public class QueueHealthIndicator implements HealthIndicator {
private final TaskQueue taskQueue;
private static final int WARNING_THRESHOLD = 100;
private static final int CRITICAL_THRESHOLD = 500;
public QueueHealthIndicator(TaskQueue taskQueue) {
this.taskQueue = taskQueue;
}
@Override
public Health health() {
int queueSize = taskQueue.size();
Health.Builder builder = new Health.Builder()
.withDetail("queueSize", queueSize)
.withDetail("warningThreshold", WARNING_THRESHOLD)
.withDetail("criticalThreshold", CRITICAL_THRESHOLD);
if (queueSize >= CRITICAL_THRESHOLD) {
return builder.down()
.withDetail("message", "Queue is critically full!")
.build();
} else if (queueSize >= WARNING_THRESHOLD) {
return builder.status("WARNING")
.withDetail("message", "Queue is getting full")
.build();
} else {
return builder.up()
.withDetail("message", "Queue is healthy")
.build();
}
}
}
Asynchronous Health Check
To avoid blocking if the check is slow:
@Component
public class SlowServiceHealthIndicator implements HealthIndicator {
private final SlowService slowService;
private volatile Health cachedHealth = Health.unknown().build();
@Scheduled(fixedRate = 30000) // Every 30 seconds
public void checkHealth() {
try {
boolean isUp = slowService.ping();
cachedHealth = isUp
? Health.up().withDetail("lastCheck", Instant.now()).build()
: Health.down().withDetail("lastCheck", Instant.now()).build();
} catch (Exception e) {
cachedHealth = Health.down()
.withDetail("error", e.getMessage())
.withDetail("lastCheck", Instant.now())
.build();
}
}
@Override
public Health health() {
return cachedHealth; // Immediately return the cache
}
}
4. Health Groups
Group Configuration
# Group for load balancer (quick checks)
management.endpoint.health.group.liveness.include=ping
management.endpoint.health.group.liveness.show-details=never
# Group for Kubernetes readiness (complete checks)
management.endpoint.health.group.readiness.include=db,redis,externalApi
management.endpoint.health.group.readiness.show-details=when_authorized
# Group for monitoring (everything)
management.endpoint.health.group.full.include=*
management.endpoint.health.group.full.show-details=always
Accessing Groups
# Basic check
curl http://localhost:8080/actuator/health
# Liveness group
curl http://localhost:8080/actuator/health/liveness
# Readiness group
curl http://localhost:8080/actuator/health/readiness
# Full group
curl http://localhost:8080/actuator/health/full
5. Kubernetes: Liveness vs Readiness
Concepts
Liveness Probe:
"Is the application alive?"
→ If DOWN: Kubernetes restarts the pod
Readiness Probe:
"Is the application ready to receive traffic?"
→ If DOWN: Kubernetes removes the pod from the load balancer
Spring Boot Configuration
# Enable Kubernetes probes
management.endpoint.health.probes.enabled=true
# Automatically created groups:
# /actuator/health/liveness
# /actuator/health/readiness
Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: my-app
image: my-app:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
Customize Groups
@Component
public class CustomLivenessIndicator implements HealthIndicator {
@Override
public Health health() {
// Minimal check: app responds
return Health.up().build();
}
}
@Component
public class CustomReadinessIndicator implements HealthIndicator {
private final DataSource dataSource;
private final RedisTemplate<?, ?> redis;
@Override
public Health health() {
// Check that dependencies are ready
try {
dataSource.getConnection().isValid(1);
redis.getConnectionFactory().getConnection().ping();
return Health.up().build();
} catch (Exception e) {
return Health.down().withException(e).build();
}
}
}
management.endpoint.health.group.liveness.include=customLiveness,ping
management.endpoint.health.group.readiness.include=customReadiness,db,redis
6. Health Check for Load Balancer
Simple Dedicated Endpoint
@RestController
public class HealthController {
private final AtomicBoolean healthy = new AtomicBoolean(true);
@GetMapping("/health/lb")
public ResponseEntity<String> lbHealthCheck() {
if (healthy.get()) {
return ResponseEntity.ok("OK");
}
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("DOWN");
}
// For maintenance: take the app out of service
@PostMapping("/admin/maintenance/enable")
public void enableMaintenance() {
healthy.set(false);
}
@PostMapping("/admin/maintenance/disable")
public void disableMaintenance() {
healthy.set(true);
}
}
7. Summary
Recommended Configuration
# Details based on authorization
management.endpoint.health.show-details=when_authorized
management.endpoint.health.show-components=when_authorized
# Enable K8s probes
management.endpoint.health.probes.enabled=true
# Custom groups
management.endpoint.health.group.liveness.include=ping,livenessState
management.endpoint.health.group.readiness.include=readinessState,db,redis
Custom Health Indicator
@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
if (isHealthy()) {
return Health.up()
.withDetail("key", "value")
.build();
}
return Health.down()
.withDetail("error", "Something wrong")
.build();
}
}
Kubernetes Groups
| Group | Usage | Include |
|---|---|---|
liveness | Pod alive? | ping, basic checks |
readiness | Ready for traffic? | db, redis, external APIs |
🎯 Next Step
Time for practice! Let's configure Actuator from A to Z.
➡️ Lab 11.1: Actuator Configuration