Circuit Breakers
Purpose
A circuit breaker is a state machine in the caller that stops calling a dependency which is
already failing, so calls fail immediately instead of waiting for a timeout. It avoids tying
up additional caller threads and connections in calls predicted to fail —
the amplification point cascading-failures names as pool exhaustion.
A breaker fails fast on rejected calls; it does not supply a successful result for them.
That protects the caller's resources even when the only honest result is a typed error. A
fallback or degraded response can preserve useful availability, but is not a prerequisite for
resource protection. Decide both the fast-failure contract and any fallback first.
States
CLOSED → OPEN failure rate or slow-call rate ≥ threshold, over ≥ minimum calls
OPEN → HALF_OPEN after the wait duration; calls before it are rejected untried
HALF_OPEN → CLOSED the configured probe sample meets success/slow-call thresholds
HALF_OPEN → OPEN the completed probe sample breaches a threshold (per implementation policy)
Workflow
The Java illustration requires Java 21+ without preview; configuration guidance is checked
against Resilience4j 2.3.0 and its CircuitBreaker guide. Inspect compiler release/toolchain,
runtime image, resolved breaker/client dependencies and Spring/programmatic integration before
using property names or decorators. Do not upgrade the project to adopt this example. Without
per-instance traffic, mapped outcomes and decorator order, keep tuning conditional and request
the smallest missing trace/configuration or control test.
- Check failures predict later calls within the proposed scope. An invalid payload is
request-specific; an independently failing tenant backend or endpoint may justify a bounded
scoped breaker. Do not make unrelated callers share that failure history.
- Decide what the caller does with a fast failure, including status/type, retry guidance,
fallback provenance and whether accepted writes may be queued.
- Put a timeout under the breaker. A breaker counts outcomes, and a call that never
returns produces none (
timeouts-and-deadlines).
- Write the failure predicate explicitly, exception type by exception type and status
class by status class. This is the decision with the largest consequence; the table is in
references/breaker-configuration.md.
- Size the window from the endpoint's traffic: sliding window type and size, the minimum
number of calls before the rate is evaluated, the failure-rate threshold, and — separately
— a slow-call rate threshold, so a dependency that is slow but returning 200s still trips.
- Bound the half-open probes and set the wait duration. Trial calls, not full traffic.
Include a half-open residence bound and fleet-wide simultaneous probe load; a probe limit
per instance is not a fleet limit.
- Instrument state and transitions, then prove both directions in a test: force the trip
under injected failure, assert the probe count, assert recovery. See
references/fallbacks-and-testing.md.
Decision block
Use a circuit breaker when:
- the call is remote and its failures are correlated — this call failing predicts the next
one failing, which is what makes past outcomes usable as a prediction
- the call holds a scarce resource while it waits: a request thread, a pooled connection
- the caller has a defined behaviour for a fast failure, and that behaviour is honest
Avoid a circuit breaker when:
- failures are independent or request-specific, so recent outcomes do not predict the next call
- the failures are per-request rather than per-dependency — validation errors, not-found,
one tenant's malformed payload. The breaker punishes every caller for one caller's bug
- traffic through that breaker is below the minimum call count that makes a rate meaningful:
it will either never trip or trip on a run of noise
- the call is in-process and the real issue is a lock, algorithm or local resource; diagnose and
bound that resource rather than using remote-health prediction
Prefer instead when:
- the saturated resource is yours and the dependency is healthy → a concurrency limit or
bulkhead (concurrency-limiting-and-bulkheads)
- you are the overloaded party and must refuse work → rate-limiting-and-load-shedding
- one call occasionally hangs but the dependency is fine → a timeout alone
Rules
- Prefer a rate/slow-call window with a minimum sample for ordinary noisy traffic. Consecutive
thresholds react faster and can fit rare calls or categorical failures, but are noise-sensitive
and miss sustained intermittent failure. Choose from traffic and failure correlation; test
false-open probability and detection time.
- State the minimum number of calls and derive it from the endpoint's rate. Below it the
breaker stays closed whatever the rate, or one failure out of two evaluates to 50%. An
endpoint serving 2 requests a minute needs a longer/count-based window, a smaller justified
sample, a categorical/consecutive signal, or no statistical breaker.
- Half-open admits a bounded number of probes, not full traffic. Reopening to the whole
request stream is a thundering herd aimed at the instance that just came back; the probe
count is a load decision — enough to be a sample, few enough to survive.
- A breaker with no slow-call criterion misses the failure mode that matters most: a
dependency answering 200 OK in 30 s exhausts the caller like an outage while the
failure-rate breaker reads 0%. Set a slow-call duration and rate, or a tight enough timeout.
- What counts as a failure decides whether the breaker works. Classify whether an outcome is
correlated across future calls in this breaker scope and consumes the protected resource. Most
validation/domain 4xx are excluded, but 408/429 and shared authentication/routing failures need
policy. Some 5xx are payload-specific bugs and should not poison unrelated calls.
- A typical library breaker's state is per instance. N instances each learn from their own traffic, so in
a partial outage some are open and some closed and the fleet degrades unevenly. Usually
acceptable; never quote a fleet-wide trip time.
- Scope the breaker to the failure domain you want to isolate. One breaker per downstream
host lets one slow endpoint open it for all of them; one on a shared resource lets one
abusive caller open it for everyone. Key per dependency and endpoint, per tenant when
tenants can be independently bad—but per-tenant keys need cardinality bounds and expiry or the
breaker registry becomes attacker-controlled memory.
- Retry composition is a decision, not a default. With
Retry(Breaker(call)) the breaker
records every attempt, so a rate threshold is reached after fewer logical calls than it
appears; with Breaker(Retry(call)) it records one outcome per logical call, but each
protected call lasts attempts × timeout + Σ backoff, distorting slow-call detection and
letting retries reach a dependency the breaker would have protected. Pick one deliberately.
- A fallback that silently returns wrong data is worse than an error. An empty list the caller
persists, a zero balance, a default entitlement that grants access — each turns an
availability incident into a data one. Mark degraded responses as degraded.
- Opening rejects new admissions; it does not cancel calls already in flight or bound closed-state
concurrency. Preserve real client deadlines/cancellation and add a bulkhead when that resource
needs a concurrency limit. A fallback inside the recorded operation can mask every backend
failure as success; record the primary outcome before applying fallback.
- Instrument the breaker as a dependency health signal: state, transitions and the rates
it computed. Alert on time spent open, not on transitions. A breaker that has never opened
is an untested hypothesis.
Deliver the breaker scope, measured traffic/sample assumptions, outcome classification and
decorator order, proposed settings, and trip/probe/recovery assertions. Separate observed state
and downstream calls from hypotheses about dependency health; report checks not executed.
Primary sources
References
- Configuring a breaker — every parameter with what it
controls and the failure a wrong value produces, the failure-predicate decision table over
status codes and exception types, retry composition arithmetic, and per-instance versus
shared state. Read before configuring or reviewing a breaker.
- Fallbacks and testing — the fallback options with the
condition making each honest, the wrong-data rule, and how to prove a breaker works: forcing
the trip, asserting the half-open probe count, asserting recovery. Read when writing the
fallback or its tests.
1---2name: circuit-breakers3description: The breaker as a state machine that stops calling a failing dependency: closed, open and half-open; choosing rate windows versus consecutive failures; why half-open admits a bounded number of probes; the failure predicate—classifying correlated dependency failures rather than blindly counting status classes—and the distinction between protecting caller resources by failing fast and providing a semantically valid fallback. Use when a breaker trips on consecutive failures, when it never trips or trips on one client's bad requests, when half-open sends full traffic at a recovering dependency, when a breaker sits on a call with no timeout under it, or when a dependency is slow rather than failing. Does not cover bulkheads (concurrency-limiting-and-bulkheads), retry policy (retries-and-backoff), the bound itself (timeouts-and-deadlines), the system-wide loop (cascading-failures), shedding (rate-limiting-and-load-shedding), or serving a cached fallback (caching-strategies).4---56# Circuit Breakers78## Purpose910A circuit breaker is a state machine in the caller that stops calling a dependency which is11already failing, so calls fail immediately instead of waiting for a timeout. It avoids tying12up additional caller threads and connections in calls predicted to fail —13the amplification point `cascading-failures` names as pool exhaustion.1415**A breaker fails fast on rejected calls; it does not supply a successful result for them.**16That protects the caller's resources even when the only honest result is a typed error. A17fallback or degraded response can preserve useful availability, but is not a prerequisite for18resource protection. Decide both the fast-failure contract and any fallback first.1920## States2122```text23CLOSED → OPEN failure rate or slow-call rate ≥ threshold, over ≥ minimum calls24OPEN → HALF_OPEN after the wait duration; calls before it are rejected untried25HALF_OPEN → CLOSED the configured probe sample meets success/slow-call thresholds26HALF_OPEN → OPEN the completed probe sample breaches a threshold (per implementation policy)27```2829## Workflow3031The Java illustration requires Java 21+ without preview; configuration guidance is checked32against Resilience4j 2.3.0 and its CircuitBreaker guide. Inspect compiler release/toolchain,33runtime image, resolved breaker/client dependencies and Spring/programmatic integration before34using property names or decorators. Do not upgrade the project to adopt this example. Without35per-instance traffic, mapped outcomes and decorator order, keep tuning conditional and request36the smallest missing trace/configuration or control test.37381. **Check failures predict later calls within the proposed scope.** An invalid payload is39 request-specific; an independently failing tenant backend or endpoint may justify a bounded40 scoped breaker. Do not make unrelated callers share that failure history.412. **Decide what the caller does with a fast failure**, including status/type, retry guidance,42 fallback provenance and whether accepted writes may be queued.433. **Put a timeout under the breaker.** A breaker counts outcomes, and a call that never44 returns produces none (`timeouts-and-deadlines`).454. **Write the failure predicate explicitly**, exception type by exception type and status46 class by status class. This is the decision with the largest consequence; the table is in47 `references/breaker-configuration.md`.485. **Size the window from the endpoint's traffic**: sliding window type and size, the minimum49 number of calls before the rate is evaluated, the failure-rate threshold, and — separately50 — a slow-call rate threshold, so a dependency that is slow but returning 200s still trips.516. **Bound the half-open probes and set the wait duration.** Trial calls, not full traffic.52 Include a half-open residence bound and fleet-wide simultaneous probe load; a probe limit53 per instance is not a fleet limit.547. **Instrument state and transitions**, then prove both directions in a test: force the trip55 under injected failure, assert the probe count, assert recovery. See56 `references/fallbacks-and-testing.md`.5758## Decision block5960```text61Use a circuit breaker when:62- the call is remote and its failures are correlated — this call failing predicts the next63 one failing, which is what makes past outcomes usable as a prediction64- the call holds a scarce resource while it waits: a request thread, a pooled connection65- the caller has a defined behaviour for a fast failure, and that behaviour is honest66Avoid a circuit breaker when:67- failures are independent or request-specific, so recent outcomes do not predict the next call68- the failures are per-request rather than per-dependency — validation errors, not-found,69 one tenant's malformed payload. The breaker punishes every caller for one caller's bug70- traffic through that breaker is below the minimum call count that makes a rate meaningful:71 it will either never trip or trip on a run of noise72- the call is in-process and the real issue is a lock, algorithm or local resource; diagnose and73 bound that resource rather than using remote-health prediction74Prefer instead when:75- the saturated resource is yours and the dependency is healthy → a concurrency limit or76 bulkhead (concurrency-limiting-and-bulkheads)77- you are the overloaded party and must refuse work → rate-limiting-and-load-shedding78- one call occasionally hangs but the dependency is fine → a timeout alone79```8081## Rules8283- Prefer a rate/slow-call window with a minimum sample for ordinary noisy traffic. Consecutive84 thresholds react faster and can fit rare calls or categorical failures, but are noise-sensitive85 and miss sustained intermittent failure. Choose from traffic and failure correlation; test86 false-open probability and detection time.87- **State the minimum number of calls and derive it from the endpoint's rate.** Below it the88 breaker stays closed whatever the rate, or one failure out of two evaluates to 50%. An89 endpoint serving 2 requests a minute needs a longer/count-based window, a smaller justified90 sample, a categorical/consecutive signal, or no statistical breaker.91- **Half-open admits a bounded number of probes, not full traffic.** Reopening to the whole92 request stream is a thundering herd aimed at the instance that just came back; the probe93 count is a load decision — enough to be a sample, few enough to survive.94- A breaker with no slow-call criterion misses the failure mode that matters most: a95 dependency answering 200 OK in 30 s exhausts the caller like an outage while the96 failure-rate breaker reads 0%. Set a slow-call duration and rate, or a tight enough timeout.97- **What counts as a failure decides whether the breaker works.** Classify whether an outcome is98 correlated across future calls in this breaker scope and consumes the protected resource. Most99 validation/domain 4xx are excluded, but 408/429 and shared authentication/routing failures need100 policy. Some 5xx are payload-specific bugs and should not poison unrelated calls.101- **A typical library breaker's state is per instance.** N instances each learn from their own traffic, so in102 a partial outage some are open and some closed and the fleet degrades unevenly. Usually103 acceptable; never quote a fleet-wide trip time.104- **Scope the breaker to the failure domain you want to isolate.** One breaker per downstream105 host lets one slow endpoint open it for all of them; one on a shared resource lets one106 abusive caller open it for everyone. Key per dependency and endpoint, per tenant when107 tenants can be independently bad—but per-tenant keys need cardinality bounds and expiry or the108 breaker registry becomes attacker-controlled memory.109- Retry composition is a decision, not a default. With `Retry(Breaker(call))` the breaker110 records **every attempt**, so a rate threshold is reached after fewer logical calls than it111 appears; with `Breaker(Retry(call))` it records one outcome per logical call, but each112 protected call lasts `attempts × timeout + Σ backoff`, distorting slow-call detection and113 letting retries reach a dependency the breaker would have protected. Pick one deliberately.114- A fallback that silently returns wrong data is worse than an error. An empty list the caller115 persists, a zero balance, a default entitlement that grants access — each turns an116 availability incident into a data one. Mark degraded responses as degraded.117- Opening rejects new admissions; it does not cancel calls already in flight or bound closed-state118 concurrency. Preserve real client deadlines/cancellation and add a bulkhead when that resource119 needs a concurrency limit. A fallback inside the recorded operation can mask every backend120 failure as success; record the primary outcome before applying fallback.121- Instrument the breaker as a **dependency health signal**: state, transitions and the rates122 it computed. Alert on time spent open, not on transitions. A breaker that has never opened123 is an untested hypothesis.124125Deliver the breaker scope, measured traffic/sample assumptions, outcome classification and126decorator order, proposed settings, and trip/probe/recovery assertions. Separate observed state127and downstream calls from hypotheses about dependency health; report checks not executed.128129## Primary sources130131- [Resilience4j CircuitBreaker guide](https://resilience4j.readme.io/docs/circuitbreaker)132- [Google SRE — Addressing Cascading Failures](https://sre.google/sre-book/addressing-cascading-failures/)133- [AWS Builders' Library — Timeouts, retries and backoff with jitter](https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/)134135## References136137- [Configuring a breaker](references/breaker-configuration.md) — every parameter with what it138 controls and the failure a wrong value produces, the failure-predicate decision table over139 status codes and exception types, retry composition arithmetic, and per-instance versus140 shared state. Read before configuring or reviewing a breaker.141- [Fallbacks and testing](references/fallbacks-and-testing.md) — the fallback options with the142 condition making each honest, the wrong-data rule, and how to prove a breaker works: forcing143 the trip, asserting the half-open probe count, asserting recovery. Read when writing the144 fallback or its tests.