A circuit breaker is easy to describe — "it stops calling a failing service" — and surprisingly hard to picture. When does it trip? What's HALF_OPEN actually for? Why does failing fast help? So I built a lab that runs the real Resilience4j state machine on a sliding window.
▶ Live demo: https://dev48v.github.io/resilience4j-lab/
Source (single file, zero deps): https://github.com/dev48v/resilience4j-lab
The state machine, live
Flip "downstream is failing" on and click call. Each result lands in a sliding window; once the failure rate crosses the threshold, the breaker trips through three states:
- CLOSED — calls pass through; the breaker just tracks the failure rate.
-
OPEN — the rate hit 50%, so calls now fail instantly with
CallNotPermittedExceptionforwaitDurationInOpenState. Nothing piles up waiting on a dead dependency — that's the whole value. A slow/dead downstream can otherwise exhaust your thread pool and take your service down with it. - HALF_OPEN — after the wait, a few trial calls are allowed through. Succeed → back to CLOSED (recovered). One fails → straight back to OPEN.
The config mirrors real Resilience4j:
CircuitBreakerConfig.custom()
.failureRateThreshold(50) // %
.slidingWindowSize(10)
.waitDurationInOpenState(Duration.ofSeconds(5))
.permittedNumberOfCallsInHalfOpenState(3)
.build();
@CircuitBreaker(name = "downstream", fallbackMethod = "fallback")
public String call() { return downstream.get(); }
Watching the sliding window fill with red and the breaker flip to OPEN at exactly the threshold makes the whole thing concrete in a way the docs don't.
Retry (and why order matters)
The Retry tab shows @Retry re-invoking a flaky call with exponential backoff — 500ms → 1s → 2s — so a brief blip is absorbed transparently. Push "fails before success" past maxAttempts and every attempt is exhausted before the exception propagates.
The gotcha the lab sets up: nest Retry inside the CircuitBreaker, not the other way round. Otherwise your retries keep hammering a service that's already down — the breaker should get the final say and fail fast. (Spring applies them by @Order; CircuitBreaker outermost.)
One index.html, works offline. If it made circuit breakers click, a star helps others find it: https://github.com/dev48v/resilience4j-lab
Top comments (0)