Careeroza
Custom Health Indicator
Checking a Dependency
ShopEase cannot take orders if the payment gateway is down. A custom HealthIndicator adds that check to /actuator/health.
@Component("paymentGateway")
@RequiredArgsConstructor
public class PaymentGatewayHealthIndicator implements HealthIndicator {
private final RestClient paymentClient;
@Override
public Health health() {
try {
long start = System.currentTimeMillis();
paymentClient.get().uri("/v1/health").retrieve().toBodilessEntity();
return Health.up().withDetail("provider", "razorpay")
.withDetail("latencyMs", System.currentTimeMillis() - start).build();
} catch (Exception e) {
return Health.down(e).withDetail("provider", "razorpay").build();
}
}
}Example
When the gateway times out the overall status becomes DOWN and the endpoint returns 503:
curl -i localhost:8081/actuator/healthExample output
HTTP/1.1 503 Service Unavailable
{"status":"DOWN","components":{"db":{"status":"UP"},"paymentGateway":{"status":"DOWN","details":{"provider":"razorpay","error":"org.springframework.web.client.ResourceAccessException: I/O error on GET request: Read timed out"}}}}How Status Is Aggregated
Example
The overall status is the "worst" status of all components. Spring's default order is DOWN, OUT_OF_SERVICE, UP, UNKNOWN:
import java.util.*;
public class Main {
enum Status { DOWN, OUT_OF_SERVICE, UP, UNKNOWN } // most severe first
static Status aggregate(Map<String, Status> components) {
return components.values().stream().min(Comparator.naturalOrder()).orElse(Status.UNKNOWN);
}
public static void main(String[] args) {
Map<String, Status> healthy = new LinkedHashMap<>();
healthy.put("db", Status.UP);
healthy.put("redis", Status.UP);
healthy.put("paymentGateway", Status.UP);
System.out.println(healthy + " -> " + aggregate(healthy));
healthy.put("paymentGateway", Status.DOWN);
System.out.println(healthy + " -> " + aggregate(healthy));
}
}Output
{db=UP, redis=UP, paymentGateway=UP} -> UP
{db=UP, redis=UP, paymentGateway=DOWN} -> DOWNLiveness vs Readiness
| Probe | Question | If it fails |
|---|---|---|
/actuator/health/liveness | is the process stuck? | Kubernetes restarts the container |
/actuator/health/readiness | can it serve traffic now? | load balancer stops sending requests |
Tip: Do not put external dependencies (payment gateway) in the liveness group: a gateway outage would restart every pod and make things worse. Put them in readiness or keep them informational.
Example
application.yml
management:
endpoint:
health:
group:
readiness:
include: readinessState,db,redis