Circuit Breaker
Three States
A circuit breaker sits between the caller and the remote service and watches results, like the electrical breaker in your home:
failure rate >= threshold
+--------+ (in the last N calls) +--------+
| CLOSED | -----------------------> | OPEN | calls are rejected at once
+--------+ +--------+ (fallback, no network call)
^ | ^
| trial calls succeed | | a trial call fails
| wait time v |
| passes +-----------+
+----------------------------- | HALF_OPEN | let a few trial calls through
+-----------+- CLOSED: normal. Calls go through; results are recorded in a sliding window.
- OPEN: too many failures. Calls fail fast with
CallNotPermittedException→ fallback. The sick service gets time to recover. - HALF_OPEN: after the wait duration, a few trial calls are allowed. Success → CLOSED, failure → OPEN again.
A Circuit Breaker in Plain Java
This state machine uses a count-based window of 5 calls, opens at 50% failures, waits 10 s in OPEN and needs 2 successful trial calls to close. product-service is down between t=2 s and t=22 s. A simulated clock keeps the output deterministic:
Example
import java.util.*;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
CircuitBreaker cb = new CircuitBreaker(5, 50.0, 10_000, 2);
long[] callTimes = {0, 1000, 2000, 3000, 4000, 5000, 8000, 14000, 20000, 24000, 25000, 26000};
for (long t : callTimes) {
Supplier<String> productService = () -> {
if (t >= 2000 && t < 22000) throw new RuntimeException("503");
return "boAt Rockerz 450";
};
String result = cb.call(t, productService, () -> "cached product");
System.out.printf("t=%5dms %-36s state=%s%n", t, result, cb.state());
}
}
}
enum State { CLOSED, OPEN, HALF_OPEN }
class CircuitBreaker {
private final int windowSize;
private final double failureRateThreshold;
private final long waitInOpenMs;
private final int trialCalls;
private final Deque<Boolean> window = new ArrayDeque<>(); // true = failed
private State state = State.CLOSED;
private long openedAt;
private int trialSuccesses;
CircuitBreaker(int windowSize, double threshold, long waitInOpenMs, int trialCalls) {
this.windowSize = windowSize;
this.failureRateThreshold = threshold;
this.waitInOpenMs = waitInOpenMs;
this.trialCalls = trialCalls;
}
State state() { return state; }
String call(long now, Supplier<String> remote, Supplier<String> fallback) {
if (state == State.OPEN) {
if (now - openedAt < waitInOpenMs) return "REJECTED -> " + fallback.get();
state = State.HALF_OPEN;
trialSuccesses = 0;
}
try {
String value = remote.get();
onSuccess();
return "OK -> " + value;
} catch (RuntimeException e) {
onFailure(now);
return "FAILED(" + e.getMessage() + ") -> " + fallback.get();
}
}
private void onSuccess() {
if (state == State.HALF_OPEN) {
if (++trialSuccesses >= trialCalls) { state = State.CLOSED; window.clear(); }
} else {
record(false);
}
}
private void onFailure(long now) {
if (state == State.HALF_OPEN) { open(now); return; }
record(true);
long failed = window.stream().filter(f -> f).count();
if (window.size() == windowSize && failed * 100.0 / windowSize >= failureRateThreshold) open(now);
}
private void record(boolean failed) {
window.addLast(failed);
if (window.size() > windowSize) window.removeFirst();
}
private void open(long now) { state = State.OPEN; openedAt = now; }
}Output
t= 0ms OK -> boAt Rockerz 450 state=CLOSED t= 1000ms OK -> boAt Rockerz 450 state=CLOSED t= 2000ms FAILED(503) -> cached product state=CLOSED t= 3000ms FAILED(503) -> cached product state=CLOSED t= 4000ms FAILED(503) -> cached product state=OPEN t= 5000ms REJECTED -> cached product state=OPEN t= 8000ms REJECTED -> cached product state=OPEN t=14000ms FAILED(503) -> cached product state=OPEN t=20000ms REJECTED -> cached product state=OPEN t=24000ms OK -> boAt Rockerz 450 state=HALF_OPEN t=25000ms OK -> boAt Rockerz 450 state=CLOSED t=26000ms OK -> boAt Rockerz 450 state=CLOSED
Read the output: at t=4 s the window is [ok, ok, fail, fail, fail] = 60% → OPEN. Calls at 5 s and 8 s never touch the network. At 14 s one trial call fails → OPEN again. At 24 s the service is back: two trial successes → CLOSED.
Note: Real Resilience4j in HALF_OPEN waits for all permitted trial calls and then compares their failure rate with the threshold. The simplified rule here ("any failure re-opens") shows the same idea.