Circuit Breaker with Resilience4j
A slow, failing downstream service doesn't just fail its own calls — it can quietly take the caller down with it. This module is the pattern that stops that from spreading.
Learning objectives
- Beginner: Explain the three states a circuit breaker can be in and what causes a transition between them.
- Intermediate: Wrap a Feign or WebClient call with a Resilience4j circuit breaker and a fallback method.
- Advanced: Tune a circuit breaker's failure-rate threshold and wait-duration for a specific service's real failure characteristics.
◆ The problem
If inventory-service starts responding slowly (not crashing — just slow), every service calling it keeps sending requests and waiting, one thread per in-flight call. Under real load, the CALLERS run out of threads waiting on a service that was never going to answer in time anyway — the failure spreads to services that were themselves perfectly healthy.
A circuit breaker interrupts this: after enough recent failures, it stops calling the downstream service ENTIRELY for a while, failing fast instead of waiting, protecting the caller's own capacity.
| State | Behavior |
|---|---|
| CLOSED | Normal — calls pass through, failures are being counted. |
| OPEN | Calls fail immediately (or go to a fallback) without even attempting the downstream call. |
| HALF_OPEN | A limited number of test calls are allowed through, to check if the downstream service has recovered. |
The transition from CLOSED to OPEN happens once the failure rate over a sliding window crosses a configured threshold; OPEN automatically moves to HALF_OPEN after a wait duration, to test recovery without fully reopening the floodgates immediately.
@CircuitBreaker(name = "inventoryService", fallbackMethod = "stockFallback") public StockLevel getStock(String sku) { return inventoryClient.getStock(sku); } public StockLevel stockFallback(String sku, Throwable t) { return StockLevel.unknown(sku); // degrade gracefully instead of failing the whole request }
The fallback method's signature must match the original plus a Throwable parameter — this is where you decide what "gracefully degraded" actually looks like for THIS specific call: a cached last-known value, a sensible default, or an honest "temporarily unavailable" response.
💻 Code example
@CircuitBreaker(name = "inventoryService", fallbackMethod = "stockFallback") public StockLevel getStock(String sku) { return inventoryClient.getStock(sku); } public StockLevel stockFallback(String sku, Throwable t) { return StockLevel.unknown(sku); }
resilience4j: circuitbreaker: instances: inventoryService: failure-rate-threshold: 50 wait-duration-in-open-state: 10s sliding-window-size: 20
▲ Pitfall
A threshold set too sensitively (e.g. tripping after just 2-3 failures) opens the circuit on ordinary transient blips, degrading a healthy service unnecessarily; one set too loosely lets real cascading failure build up before the breaker ever engages. Getting this right requires knowing the downstream service's REAL baseline failure rate under normal conditions, not guessing a round number.
✓ Quick recap
- A circuit breaker stops one slow/failing downstream service from exhausting its callers' own thread pools by failing fast instead of waiting.
- CLOSED (normal) → OPEN (failing fast) → HALF_OPEN (testing recovery) → back to CLOSED or OPEN.
- A fallback method decides what graceful degradation actually means for this specific call — not just "return an error."
- Threshold tuning requires knowing the real downstream service's baseline failure rate, not a guessed round number.
Want a visual for this concept?
Generate a diagram tailored to “Circuit Breaker with Resilience4j” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.
Sign in to generate a visual →