Crux Read real breaker config and call-site code, predict the state transitions and trip behaviour, and pick the fix a senior engineer would make first.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 14 min
A breaker’s behaviour is decided in its config and at its call site. Read each snippet, predict what the breaker actually does under load, and choose the change a senior engineer would make first.
Goal
Practise the loop you run on every breaker review: read the config and the call wrapping, predict the state transitions and trip behaviour, and spot the misconfiguration that makes the breaker fire wrong — or not at all.
Snippet 1 — the state machine
// resilience4j: walk a single breaker through one incidentCircuitBreaker cb = registry.circuitBreaker("payments");// 1. closed: calls pass, failures countedcb.executeSupplier(() -> paymentClient.charge(req)); // okcb.executeSupplier(() -> paymentClient.charge(req)); // throws -> counted// ... failure rate crosses failureRateThreshold ...// breaker transitions: CLOSED -> OPEN, starts waitDurationInOpenStatecb.executeSupplier(() -> paymentClient.charge(req)); // <- what happens here?
Quiz
Completed
The breaker has just transitioned CLOSED to OPEN and the cooldown timer is running. The next executeSupplier call arrives. What happens, and what is the dependency's load right now?
Heads-up OPEN is the rejecting state — it short-circuits every call without touching the dependency. Counting happens in CLOSED; OPEN actively blocks.
Heads-up Half-open is entered only after waitDurationInOpenState elapses. Until the cooldown fires, the breaker stays OPEN and rejects.
Heads-up OPEN does not queue calls for later — it rejects them instantly so the caller can fall back. Queueing would re-pin the resources the breaker exists to free.
Snippet 2 — the threshold config
CircuitBreakerConfig.custom() .slidingWindowType(SlidingWindowType.COUNT_BASED) .slidingWindowSize(100) .failureRateThreshold(50) // trip at 50% failures .minimumNumberOfCalls(100) // ...but only after 100 calls .build();// endpoint: /admin/report -> ~6 requests per minute
Quiz
Completed
This config is applied to a low-traffic admin endpoint serving roughly 6 requests/minute. The dependency behind it goes hard-down. How does the breaker behave, and why?
Heads-up Below minimumNumberOfCalls the breaker stays closed regardless of failure rate. With 100 required and 6/min, it cannot trip for about 17 minutes — the floor is mis-sized for this traffic.
Heads-up The threshold is a rate, not a raw count, and it cannot even be evaluated until 100 calls are seen. The problem is the floor blocking the trip, not the rate.
Heads-up It has a large effect: at 6/min, 100 calls span about 17 minutes, so a count-based window of 100 reacts very slowly here. That is exactly why a time-based window is often preferred for low traffic.
Snippet 3 — the half-open probe
CircuitBreakerConfig.custom() .waitDurationInOpenState(Duration.ofSeconds(10)) .permittedNumberOfCallsInHalfOpenState(10) // automaticTransitionFromOpenToHalfOpenEnabled = false (default) .build();// dependency recovered at second 4; breaker tripped at second 0// no calls arrive between second 0 and second 30
Quiz
Completed
The dependency recovered at second 4 and the cooldown is 10 s, but no calls arrive between second 0 and second 30. When does this breaker actually probe and reopen to traffic?
Heads-up Auto-transition is disabled by default, so the breaker does not probe on the timer alone. It needs an incoming call after the cooldown to move to half-open.
Heads-up A breaker has no direct view of the dependency's health — it only learns from call outcomes. It cannot know recovery happened at second 4 without sending a probe call.
Heads-up It stays open only until the next call after the cooldown arrives; that call triggers half-open. It is not permanently stuck — it is waiting for traffic to probe with.
Snippet 4 — the timeout interplay
// breaker counts failures, but the call has no time budgetSupplier<Resp> guarded = CircuitBreaker .decorateSupplier(cb, () -> recsClient.fetch(req)); // can hang 30s// during the incident recsClient.fetch never errors -- it just hangsResp r = guarded.get();
Quiz
Completed
During the incident recsClient.fetch stops erroring and instead hangs for 30 s per call. The breaker is wired but never trips. What is the single highest-leverage fix?
Heads-up No failures are being recorded at all, so any threshold is irrelevant. The hang must first be turned into a countable failure by a timeout.
Heads-up A bigger window still records zero failures, because a hang without a timeout is neither success nor failure. The window cannot capture events that never register.
Heads-up Retrying a 30 s hang multiplies the resource cost and never makes the breaker count it. The fix is a timeout that converts the hang into a failure the breaker can see.
Recap
Breaker behaviour is read in config and at the call site. OPEN short-circuits every call instantly so the dependency gets zero load; a minimum-volume floor sized for high traffic silently blocks tripping on a low-traffic endpoint (use a smaller floor or a time-based window); with automatic transition disabled the breaker probes only when a call arrives after the cooldown, never on the timer alone, so an idle breaker stays open until traffic returns; and a breaker with no timeout is blind to a hang, because only a time budget converts a hang into a countable failure. Diagnose from the config and the wrapping, then fix the one setting that makes the breaker fire correctly.