What does the @CircuitBreaker annotation do, and what are the three states of a Resilience4j circuit breaker?
answer
- CLOSED -> OPEN -> HALF_OPEN cycle
- OPEN = fail fast, CallNotPermittedException
- sliding window records outcomes
- minimum-number-of-calls before evaluating
- fallbackMethod + trailing Throwable
basics
~20 s@CircuitBreaker wraps a method so repeated failures 'trip' the breaker and stop further calls, failing fast instead of hammering a broken dependency. The three states are CLOSED (normal), OPEN (blocking calls), and HALF_OPEN (testing recovery).
solid answer
~40 s@CircuitBreaker (from resilience4j-spring-boot) wraps a method and tracks its success/failure rate over a sliding window. In CLOSED state calls pass through and outcomes are recorded. When the failure rate crosses a configured threshold, it transitions to OPEN and short-circuits every call immediately (throwing CallNotPermittedException), giving the failing dependency time to recover. After a wait duration it moves to HALF_OPEN, permitting a limited number of trial calls; if they mostly succeed it returns to CLOSED, otherwise back to OPEN. You annotate the method with @CircuitBreaker(name = "backendA", fallbackMethod = "fallback") and configure thresholds under resilience4j.circuitbreaker in application.yml. The fallback method must share the signature plus a trailing Throwable parameter.
code
java · 26 linesimport io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
@Service
public class InventoryClient {
private final RestClient restClient;
public InventoryClient(RestClient restClient) {
this.restClient = restClient;
}
@CircuitBreaker(name = "inventory", fallbackMethod = "fallbackStock")
public int stockLevel(String sku) {
return restClient.get()
.uri("/stock/{sku}", sku)
.retrieve()
.body(Integer.class);
}
// Same signature + trailing Throwable. Called on failure OR when breaker is OPEN.
private int fallbackStock(String sku, Throwable t) {
return 0; // degrade gracefully
}
}go deeper
Know the three states and that the annotation makes failing calls fail fast with an optional fallback.
Explain failure-rate vs slow-call rate, minimum-number-of-calls, and the fallback method signature rule.
Discuss sliding-window sizing, tuning wait-duration and half-open trials, and CallNotPermittedException handling in fallbacks.
Reason about cascading-failure prevention across a service mesh, per-instance vs shared breaker registries, and observability via CircuitBreaker events/metrics.
**What it is.** Resilience4j is a lightweight fault-tolerance library that replaced Netflix Hystrix as the standard in the Spring ecosystem. With the `resilience4j-spring-boot3` starter, you get an AOP-based `@CircuitBreaker` annotation (from `io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker`) that you place on any Spring bean method. **The problem it solves.** If a downstream service (DB, REST API) is slow or down, naively retrying or blocking threads on it can exhaust your own thread pool and cascade the failure upstream. A circuit breaker detects the failing dependency and 'fails fast' — returning an error or fallback immediately instead of waiting on a doomed call. **The three main states:** - **CLOSED** — normal operation. Every call is allowed through and its outcome (success, exception, slow call) is recorded in a sliding window. This is the starting state. - **OPEN** — the breaker has 'tripped'. Calls are rejected instantly without invoking the protected method, throwing `CallNotPermittedException`. The breaker stays OPEN for a configured `wait-duration-in-open-state`. - **HALF_OPEN** — after the wait duration elapses, the breaker allows a limited number of trial calls (`permitted-number-of-calls-in-half-open-state`). If the failure rate of those trials is below the threshold it goes back to CLOSED; otherwise it returns to OPEN. **Two special states** you may also see: **DISABLED** (always allow, no metrics) and **FORCED_OPEN** (always reject) — set manually/programmatically, they never transition automatically. **What counts as a failure.** By default any exception thrown by the method counts. You can narrow this with `record-exceptions` / `ignore-exceptions`. There is also a separate **slow-call** dimension: calls exceeding `slow-call-duration-threshold` count toward `slow-call-rate-threshold`, which can trip the breaker even when nothing throws. **Key config (application.yml).** ```yaml resilience4j.circuitbreaker.instances.backendA: sliding-window-type: COUNT_BASED sliding-window-size: 10 failure-rate-threshold: 50 wait-duration-in-open-state: 10s permitted-number-of-calls-in-half-open-state: 3 minimum-number-of-calls: 5 ``` `minimum-number-of-calls` is a crucial gotcha: the failure rate is only evaluated after that many calls are recorded, so the breaker won't trip on the very first failure. **Fallback.** `fallbackMethod` names a method in the same class with the identical parameter list plus a trailing `Throwable` (or a more specific exception type). It's invoked both when the method fails and when the breaker is OPEN (`CallNotPermittedException`). **When to use.** Around remote calls to services that can fail or slow down. Don't wrap pure in-memory logic.
- Why does the breaker not trip on the first failure even if failure-rate-threshold is 50%?Because of minimum-number-of-calls. The failure rate is only calculated once at least that many calls have been recorded in the sliding window; before that, the breaker stays CLOSED regardless of the rate.
- What exception is thrown when a call is rejected in the OPEN state?CallNotPermittedException (a subclass of RuntimeException). If a fallbackMethod is defined it is invoked with this throwable instead of propagating.
saying these in an interview costs you the question
- Saying the breaker trips immediately on the first error (ignores minimum-number-of-calls)
- Confusing HALF_OPEN with OPEN — thinking HALF_OPEN blocks all calls
- Claiming Resilience4j is the same as Hystrix / requires a Netflix dependency
- Thinking only thrown exceptions trip the breaker (forgetting slow-call rate)