What is a circuit breaker, and how does Resilience4j implement the pattern in Spring Boot?
Quick Answer
A circuit breaker prevents a calling service from repeatedly attempting calls to a downstream dependency that's already failing or timing out, since continuing to hammer a struggling dependency (and tying up the caller's own threads/connections waiting on it) can make things worse and cause the failure to cascade upstream. It tracks recent call outcomes and, once a failure-rate threshold is crossed, 'opens' (short-circuits further calls immediately, typically to a fallback, without even attempting the real call) for a cooldown period, then allows a limited number of trial calls through in a 'half-open' state to test whether the dependency has recovered before fully 'closing' again. Resilience4j is the modern, lightweight library Spring Boot integrates with (via Spring Cloud Circuit Breaker or direct Resilience4j annotations) to implement this pattern declaratively.
Detailed Answer
When Service A calls a downstream Service B that's failing, slow, or timing out, naively retrying every request makes the problem worse in two ways: it keeps tying up Service A's own threads/connections waiting on a dependency that's unlikely to respond in time, and it adds even more load onto an already-struggling Service B — potentially delaying its recovery, or causing the failure to cascade further upstream as Service A itself becomes slow/unresponsive to its own callers.
The circuit breaker pattern (named by analogy to an electrical circuit breaker) addresses this by tracking recent call outcomes and, once failures cross a threshold, stopping further calls entirely for a while — "tripping open" — instead of continuing to try and fail:
States:
- Closed (normal operation): calls pass through normally; the breaker tracks the recent success/failure rate.
- Open: once the failure rate crosses a configured threshold, the breaker "trips" — further calls are short-circuited immediately (typically routed to a fallback) without even attempting the real call, for a configured cooldown period. This protects both the struggling downstream service (no more load piling on) and the calling service (no more threads tied up waiting on a call unlikely to succeed).
- Half-Open: after the cooldown, the breaker allows a small number of trial calls through to test whether the dependency has actually recovered — if they succeed, it transitions back to Closed; if they still fail, it goes back to Open for another cooldown period.
Resilience4j is the modern, lightweight library Spring Boot integrates with for this (having succeeded Netflix Hystrix, which is now in maintenance mode) — typically applied declaratively via annotations:
@Service
class InventoryClient {
@CircuitBreaker(name = "inventoryService", fallbackMethod = "fallbackStock")
int getStockLevel(String productId) {
return restClient.get("/inventory/" + productId, Integer.class);
}
int fallbackStock(String productId, Throwable t) {
return -1; // or a cached/default value — some sensible degraded behavior instead of an exception
}
}
resilience4j:
circuitbreaker:
instances:
inventoryService:
failure-rate-threshold: 50 # trip open once 50% of recent calls fail
wait-duration-in-open-state: 10s # cooldown before trying half-open
sliding-window-size: 20 # evaluate failure rate over the last 20 calls
Complementary patterns Resilience4j also provides, often combined with a circuit breaker: retry (with backoff, for transient failures), rate limiter (cap outgoing call rate), bulkhead (isolate resources for one dependency so its failure can't starve threads needed for calls to other, healthy dependencies), and time limiter (enforce a call timeout).
The key mental model: a circuit breaker's goal isn't to make a failing call succeed — it's to fail fast once failure is likely, protecting both sides of the call and giving the struggling dependency room to recover, rather than compounding the problem with an unbounded pile of hung, retrying requests.