# 2. The circuit breaker, verified [Previous: 01-two-resilience-stacks.md](01-two-resilience-stacks.md) | [README](../README.md) | Next: [03-spring-retryable.md](03-spring-retryable.md) Source: [`R4jPaymentService.java`](../src/main/java/com/ankurm/resilience/r4j/R4jPaymentService.java). Config: [`application.yml`](../src/main/resources/application.yml). Test: [`CircuitBreakerTripAndRecoverTest.java`](../src/test/java/com/ankurm/resilience/r4j/CircuitBreakerTripAndRecoverTest.java). Transcript: [`docs/output/01-circuitbreaker-trip.txt`](output/01-circuitbreaker-trip.txt). ## The state machine ``` CLOSED --(failure rate >= threshold, over >= minimumNumberOfCalls)--> OPEN OPEN --(waitDurationInOpenState elapses)--> HALF_OPEN HALF_OPEN --(permittedNumberOfCallsInHalfOpenState calls all succeed)--> CLOSED HALF_OPEN --(a probe call fails)--> OPEN ``` This repo's config: `slidingWindowSize=10`, `minimumNumberOfCalls=5`, `failureRateThreshold=50`, `waitDurationInOpenState=2s`, `permittedNumberOfCallsInHalfOpenState=2`. ## What the transcript actually shows Calls 1-5 against a dead downstream: each one really calls the downstream, gets a `DownstreamUnavailableException`, and the fallback method runs. By call 5 the breaker has seen 5 calls (the configured minimum) at a 100% failure rate and flips to `OPEN`. Call 6 is the first call that does **not** touch the downstream at all — it gets `CallNotPermittedException` instead, straight from the breaker, before `R4jPaymentService.pay()`'s body ever runs. Calls 7-9 (phase 2 in the transcript) confirm the downstream call counter is frozen at 5 for the rest of the OPEN period — three more calls, zero more downstream traffic. This is the property `@Retryable` cannot offer on its own (see [chapter 3](03-spring-retryable.md)): a circuit breaker remembers that the dependency is down and stops asking. After `waitDurationInOpenState` (2s; the test waits 2.2s to be safely past it) the breaker allows exactly `permittedNumberOfCallsInHalfOpenState` probe calls through. The downstream was reconfigured to succeed by then, both probes pass, and the breaker closes. ## `minimumNumberOfCalls` and why it exists A fresh breaker with one failed call and no `minimumNumberOfCalls` floor would trip on pure noise — one deployment-time connection refused, one cold JVM. `minimumNumberOfCalls=5` here means the first four failures cannot trip anything by themselves; the breaker needs a real sample before it judges the dependency. ## Actuator health vs the circuitbreakers endpoint With `management.endpoint.health.show-details: always`, `GET /actuator/health` reports only an aggregate `"circuitBreakers":{"status":"UNKNOWN"}` in this version — no per-instance detail. The full breakdown (`state`, `failureRate`, `bufferedCalls`, `notPermittedCalls`, ...) is at `GET /actuator/circuitbreakers` instead. Both captured live, breaker actually OPEN, via [`ActuatorHealthTest.java`](../src/test/java/com/ankurm/resilience/r4j/ActuatorHealthTest.java): [`05-actuator-health-tripped.txt`](output/05-actuator-health-tripped.txt). - Resilience4j reference: [CircuitBreaker](https://resilience4j.readme.io/docs/circuitbreaker) (`rel=nofollow`) - The actual states and transition rules, from the CircuitBreaker interface itself, are worth reading directly rather than from a diagram: `io.github.resilience4j.circuitbreaker.CircuitBreaker.State`. Next: [03-spring-retryable.md](03-spring-retryable.md).