skip to content

Circuit Breaker Abstraction

The Spring Cloud Circuit Breaker SPI wraps a call with a fallback while keeping your code independent of Resilience4j or any other implementation. Interviewers like it because it separates the resilience pattern from the library that implements it.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

4

What is the Spring Cloud Circuit Breaker abstraction and what problem does it solve?

level: juniorimportance: must knowfreq 55%

answer

  1. Provider-neutral SPI: factory + run(supplier, fallback)
  2. CLOSED -> OPEN -> HALF_OPEN
  3. Resilience4j is the plugged-in engine
  4. App code depends only on Spring interfaces
  5. starter-circuitbreaker-resilience4j

basics

~10 s

It is a common Spring API (CircuitBreakerFactory / CircuitBreaker) for wrapping risky calls with a circuit breaker. Your code calls the abstraction, and the actual implementation (like Resilience4j) is plugged in separately.

solid answer

~40 s

Spring Cloud Circuit Breaker is a provider-neutral abstraction over circuit-breaker implementations. A circuit breaker protects a call to a remote/failing dependency: after repeated failures it 'opens' and short-circuits further calls (failing fast or running a fallback) instead of hammering a broken service. The abstraction gives you two Spring interfaces — CircuitBreakerFactory to create a named CircuitBreaker, and CircuitBreaker with run(supplier) / run(supplier, fallback) to execute the protected code. Your application code depends only on those Spring types; the concrete engine (Resilience4j is the standard one, via spring-cloud-starter-circuitbreaker-resilience4j) is chosen by which starter is on the classpath. That decouples business logic from any specific resilience library, so you can swap implementations without touching call sites.

code

java · 21 lines
java
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

@Service
class QuoteService {
    private final CircuitBreakerFactory<?, ?> cbFactory; // Spring SPI, not Resilience4j
    private final RestClient restClient;

    QuoteService(CircuitBreakerFactory<?, ?> cbFactory, RestClient restClient) {
        this.cbFactory = cbFactory;
        this.restClient = restClient;
    }

    String quoteOfTheDay() {
        return cbFactory.create("quotes").run(
            () -> restClient.get().uri("/quote").retrieve().body(String.class),
            throwable -> "Fallback: no quote available" // used on failure or open circuit
        );
    }
}

go deeper

for a junior

Know it wraps risky calls and swaps in Resilience4j under the hood; name factory + run(supplier, fallback).

for a middle

Explain the three states and that the id keys per-call-site config and state.

for a senior

Articulate the decoupling value and how the provider is selected by classpath/starter.

for a principal

Frame it as an SPI/portability decision and know the reactive variant and bundled Resilience4j modules exist.

## The pattern A **circuit breaker** is a resilience pattern (from Michael Nygard's *Release It!*). When a downstream dependency (an HTTP service, a DB, a queue) starts failing or timing out, keep calling it and you waste threads, pile up latency, and can cascade the failure across your system. A circuit breaker watches the outcome of calls and moves through three states: - **CLOSED** — calls pass through normally; failures are counted. - **OPEN** — once a failure threshold is crossed, calls are *short-circuited*: the breaker rejects them immediately (throwing quickly or invoking a fallback) without touching the dependency. - **HALF_OPEN** — after a wait period, a few trial calls are allowed; if they succeed the breaker closes again, otherwise it re-opens. ## What Spring Cloud Circuit Breaker adds The **Spring Cloud Circuit Breaker** project does *not* implement the breaker itself. It defines a thin **SPI (Service Provider Interface)** — a set of Spring interfaces in `org.springframework.cloud.client.circuitbreaker`: - **`CircuitBreakerFactory`** — a factory bean. You call `factory.create("someId")` to get a `CircuitBreaker` keyed by a string id. The id lets each logical call site have its own configuration and its own independent state. - **`CircuitBreaker`** — the thing you execute code through. Its core methods are: - `<T> T run(Supplier<T> toRun)` - `<T> T run(Supplier<T> toRun, Function<Throwable, T> fallback)` Your app depends only on these two interfaces. The actual engine is supplied by a starter on the classpath. The reference implementation is **Resilience4j**, pulled in via `spring-cloud-starter-circuitbreaker-resilience4j`, which auto-configures a `Resilience4JCircuitBreakerFactory`. (A reactive variant, `ReactiveCircuitBreakerFactory` / `ReactiveCircuitBreaker`, exists for `Mono`/`Flux`.) ## Why decouple Without the abstraction you would import Resilience4j types (or Hystrix, Spring Retry, etc.) directly into your service classes. That couples business logic to a specific library's API and to its threading/config model. With the abstraction: - Call sites are written once against Spring interfaces. - Swapping the provider is a dependency/config change, not a code change. - Configuration is centralized in `Customizer` beans (see related questions), keeping tuning out of call sites. ## When to use Use it around any call to a resource that can be slow or fail independently of your app: REST/gRPC calls to other services, third-party APIs, sometimes DB or cache calls. It is most valuable in microservice/distributed systems where a single sick dependency can otherwise drag down callers. ## Gotchas / edge cases - **It is not automatic** — the abstraction is programmatic; wrapping code in `run(...)` (or using the separate Resilience4j annotation leaf) is your responsibility. - **The id matters** — breakers are per-id; reuse the same id where you want shared state, distinct ids where you want isolation. - **A fallback is optional** — with the single-arg `run`, a failure (or open circuit) propagates as an exception; with the two-arg form the `Function<Throwable,T>` is invoked instead. - **Timeouts/bulkheads** — Resilience4j bundles TimeLimiter, Bulkhead, Retry, RateLimiter with the circuit breaker; the Spring abstraction surfaces them through the same factory's configuration but the `run` API you code against stays the same.

  • Which dependency plugs the actual implementation in, and what happens if none is present?
    spring-cloud-starter-circuitbreaker-resilience4j auto-configures a Resilience4JCircuitBreakerFactory. Without any provider starter, no CircuitBreakerFactory bean is auto-configured, so injecting it fails and you have nothing to run calls through.
  • Does the abstraction start the breaker for you or do you have to wrap calls yourself?
    You wrap them yourself: the abstraction is programmatic — you must route the risky call through CircuitBreaker.run(...). It does not intercept arbitrary method calls (that is the separate annotation-based leaf).

saying these in an interview costs you the question

  • Thinking Spring Cloud Circuit Breaker implements the breaker itself rather than delegating to Resilience4j
  • Believing calls are protected automatically without wrapping them in run(...)
  • Confusing it with a load balancer or retry mechanism

context

open as a page

How do you execute a protected call with a fallback using the CircuitBreaker abstraction, and when exactly is the fallback invoked?

level: middleimportance: must knowfreq 50%

basics

~10 s

Get a CircuitBreaker from the factory (factory.create("id")) and call run(supplier, fallback). The supplier holds the risky call; the fallback runs when that call throws or when the circuit is open, receiving the Throwable.

open as a page

How do you configure per-id and default circuit-breaker settings while keeping application code decoupled from Resilience4j?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Register a Customizer bean for the provider factory (e.g. Customizer<Resilience4JCircuitBreakerFactory>). In it, set a default config and per-id overrides via configureDefault/configure. Call sites still only use CircuitBreakerFactory.run, so they stay decoupled.

open as a page

Why would a team adopt the Spring Cloud Circuit Breaker abstraction instead of coding directly against Resilience4j, and what are the trade-offs?

level: principalimportance: should knowfreq 30%

basics

~20 s

The abstraction gives one Spring API for all call sites and keeps business code free of any specific resilience library, so you could swap engines or standardize across services. The cost is a thinner feature surface and config still tied to the provider.

open as a page