CircuitBreaker Half-Open State#
The HALF_OPEN state is a transitional probe state in the resilience4j CircuitBreaker state machine. When the CircuitBreaker leaves OPEN, it enters HALF_OPEN to test whether the downstream service has recovered by allowing a limited number of trial calls through before deciding to close or re-open.
Key Configuration Parameters#
All parameters are set on CircuitBreakerConfig.Builder:
| Parameter | Default | Purpose |
|---|---|---|
permittedNumberOfCallsInHalfOpenState | 10 | Number of trial calls allowed while in HALF_OPEN |
maxWaitDurationInHalfOpenState | 0 (disabled) | Optional timeout after which the breaker force-transitions out of HALF_OPEN |
transitionToStateAfterWaitDuration | OPEN | Target state when maxWaitDurationInHalfOpenState elapses (OPEN or CLOSED) |
In HALF_OPEN, the trial call budget is governed by permittedNumberOfCallsInHalfOpenState . The half-open metrics window uses permittedNumberOfCallsInHalfOpenState as its size, and the minimum number of calls before rates are evaluated is capped at min(minimumNumberOfCalls, permittedNumberOfCallsInHalfOpenState). With the defaults (100 / 10) this is permittedNumberOfCallsInHalfOpenState, but a lower configured minimumNumberOfCalls takes precedence.
How HALF_OPEN Works#
Entry β The breaker enters HALF_OPEN from OPEN via two paths :
- Automatic: If
automaticTransitionFromOpenToHalfOpenEnabled = true, a background scheduler fires afterwaitDurationInOpenStateelapses. - Manual: The first
tryAcquirePermission()call afterwaitDurationInOpenStatehas elapsed triggers the transition inline.
Call limiting β The HalfOpenState constructor initializes an AtomicInteger to permittedNumberOfCallsInHalfOpenState. Each call atomically decrements this counter; once it reaches zero, further calls are rejected with CallNotPermittedException .
Exit conditions β After every recorded success or error, checkIfThresholdsExceeded() evaluates outcome:
- Failure rate or slow-call rate exceeds configured thresholds β transitions to
OPEN - Both rates are below thresholds (and enough calls have completed to evaluate them:
min(minimumNumberOfCalls, permittedNumberOfCallsInHalfOpenState)) β transitions toCLOSED
The transition is protected by an AtomicBoolean isHalfOpen CAS, preventing concurrent state changes .
Timeout path β If maxWaitDurationInHalfOpenState β₯ 1 ms, a ScheduledExecutorService task is registered at HALF_OPEN entry. When it fires, it forces a transition to transitionToStateAfterWaitDuration (OPEN or CLOSED) regardless of how many trial calls have run . The scheduled task is cancelled if a natural transition occurs first .
Javadoc Fix: maxWaitDurationInHalfOpenState#
The Javadoc for Builder.maxWaitDurationInHalfOpenState() previously described the default HALF_OPEN exit incorrectly:
"By default CircuitBreaker will stay in Half Open state until
minimumNumberOfCallsis completed with either success or failure."
This was reported in issue #1091 and issue #2518. The Javadoc now says that, by default, the CircuitBreaker stays in Half Open until enough calls have completed, with either success or failure, to evaluate the failure and slow call rates: permittedNumberOfCallsInHalfOpenState, or minimumNumberOfCalls if that is lower. In other words, the threshold is min(minimumNumberOfCalls, permittedNumberOfCallsInHalfOpenState). The runtime behavior was always correct β the bug was documentation only.
The same incorrect term (minimumNumberOfCalls) also appeared in the public docs at resilience4j.readme.io and was reported in issue #1091 .
Source File References#
| File | Role |
|---|---|
CircuitBreakerConfig.java | All HALF_OPEN configuration parameters and their defaults |
CircuitBreakerStateMachine.java | HalfOpenState inner class β entry, call limiting, exit logic, timeout scheduling |
CircuitBreakerMetrics.java | forHalfOpen() factory β creates COUNT_BASED metrics with window size = permittedNumberOfCallsInHalfOpenState |