skip to content

Declarative Clients & Resilience

Calling other services without bringing yourself down: declarative Feign clients, load-balanced clients, the circuit-breaker abstraction, Resilience4j, and fallbacks. Interviewers ask about cascading failure, and every answer draws from here.

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

explore

questions

23

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

What is Spring Cloud OpenFeign, and how do you declare and enable a Feign client?

level: juniorimportance: must knowfreq 78%

basics

~10 s

OpenFeign lets you call another HTTP service by declaring a Java interface annotated with @FeignClient. You add @EnableFeignClients to a config class, and Spring generates a proxy that turns method calls into HTTP requests.

open as a page

What is a fallback method in a Resilience4j circuit breaker, and why do you need one?

level: juniorimportance: must knowfreq 70%

basics

~20 s

A fallback is an alternative method that runs when the protected call fails or the circuit is open. Instead of throwing an error to the caller, it returns a safe default (cached, empty, or degraded) response so the app keeps working.

open as a page

In @FeignClient(name = "order-service"), what is that name and how does the call reach a real host?

level: juniorimportance: must knowfreq 70%

basics

~20 s

The name is a logical service id, not a hostname. Feign hands it to Spring Cloud LoadBalancer, which asks the DiscoveryClient for live instances of that service and picks one, so you never hard-code a URL.

open as a page

What does the @CircuitBreaker annotation do, and what are the three states of a Resilience4j circuit breaker?

level: juniorimportance: must knowfreq 78%

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).

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

In Spring Cloud OpenFeign, what is the difference between `fallback` and `fallbackFactory`, and when do you use each?

level: middleimportance: must knowfreq 65%

basics

~20 s

Both give a Feign client a degraded implementation when calls fail. fallback points to a bean implementing the client interface with fixed default responses. fallbackFactory implements FallbackFactory<T> and receives the cause Throwable, so the fallback can vary by error.

open as a page

How do ErrorDecoder and RequestInterceptor work in Feign, and when would you customize each?

level: seniorimportance: must knowfreq 62%

basics

~20 s

A RequestInterceptor mutates every outgoing request (e.g. add an auth header) before it is sent. An ErrorDecoder converts a non-2xx HTTP response into an exception you choose, letting you map status codes to domain exceptions or trigger retries.

open as a page

How do you stop a single failing dependency from cascading and taking down the whole service? Which Resilience4j patterns combine to achieve failure isolation?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Isolate the failing call so it can't exhaust shared resources: a circuit breaker stops hammering a dead dependency, a bulkhead caps how many concurrent calls (and threads) it can consume, a time limiter bounds waiting, and a fallback returns a degraded response instead of an error.

open as a page

What roles do the Encoder and Decoder play in a Feign client, and how do you customize them?

level: middleimportance: should knowfreq 48%

basics

~20 s

The Encoder serializes a method argument (like a @RequestBody object) into the HTTP request body; the Decoder deserializes the HTTP response body into the method's return type. By default Spring Cloud OpenFeign uses Spring's HttpMessageConverters (Jackson JSON).

open as a page

How do MVC-style annotations work on a Feign client, and what is the Contract responsible for?

level: middleimportance: should knowfreq 58%

basics

~10 s

Spring Cloud OpenFeign uses a SpringMvcContract so you can annotate Feign methods with @GetMapping, @PathVariable, @RequestParam, @RequestBody, @RequestHeader — the same annotations as MVC controllers. The Contract parses these into a request template.

open as a page

Explain the difference between the name and url attributes of @FeignClient and when load balancing kicks in.

level: middleimportance: should knowfreq 55%

basics

~10 s

name is a logical service id resolved through discovery and load-balanced across instances. url is a hard-coded absolute address that skips discovery entirely. If url is present, no load balancing happens.

open as a page

How do you configure a Resilience4j instance in application.yml, and how do fallback methods and default/instance config interact?

level: middleimportance: should knowfreq 62%

basics

~20 s

You define named instances under resilience4j.<pattern>.instances.<name> in application.yml (thresholds, window, timeouts), optionally inheriting from a configs.default block. The annotation's name attribute must match the instance name; fallbackMethod names a same-signature method with a trailing 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

How do you apply configuration (encoders, interceptors, log level) to one specific Feign client without affecting all of them?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Use @FeignClient(configuration = MyConfig.class). Each client gets its own child application context, so beans in that config apply only to that client. Critically, don't put @Configuration + component-scan on it, or it leaks to everyone.

open as a page

How do you configure connect and read timeouts for a load-balanced Feign client, and what's the difference?

level: seniorimportance: should knowfreq 50%

basics

~10 s

Set spring.cloud.openfeign.client.config.<clientName>.connectTimeout and readTimeout (milliseconds); use client name 'default' for all clients. connectTimeout limits establishing the TCP connection; readTimeout limits waiting for the response after the request is sent.

open as a page

Explain the two @Bulkhead implementations in Resilience4j (semaphore vs thread-pool) and how @Bulkhead relates to @TimeLimiter.

level: seniorimportance: should knowfreq 48%

basics

~20 s

A bulkhead caps concurrent calls to protect resources. SEMAPHORE bulkhead limits concurrency on the caller's own thread; THREADPOOL bulkhead runs calls on a bounded, separate thread pool with a queue and returns a CompletableFuture. TimeLimiter caps how long an async (thread-pool) call may run.

open as a page

How does the circuit breaker's sliding window work — count-based vs time-based — and how are failure and slow-call rates evaluated?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The sliding window stores recent call outcomes. COUNT_BASED keeps the last N calls; TIME_BASED keeps calls from the last N seconds. The breaker computes failure-rate and slow-call-rate over the window and trips to OPEN when either crosses its threshold, but only after minimum-number-of-calls.

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

How does Feign client configuration scoping work, and what pitfalls arise with default-config, contextId, and shared interfaces?

level: principalimportance: should knowfreq 40%

basics

~20 s

Each Feign client gets its own child application context holding its Encoder, Decoder, Contract, interceptors, etc. Beans in @FeignClients(defaultConfiguration=...) or the client's own configuration apply per-client; a @Configuration under component scan applies globally. Properties in application.yml can override code config.

open as a page

How do you design graceful degradation under a partial outage — deciding per endpoint what a fallback should actually return?

level: principalimportance: should knowfreq 35%

basics

~20 s

Classify each dependency by how critical it is. For non-critical/enrichment calls, return cached or default data and mark the response degraded. For critical writes, fail fast rather than fake success. Never let a fallback hide data loss or return misleading 'success'.

open as a page

How does a load-balanced Feign client behave when the chosen instance is down, and how do you make it resilient?

level: principalimportance: should knowfreq 35%

basics

~20 s

By default a single failing instance just surfaces the error. Add spring-retry so the load balancer retries on another instance, tune per-attempt timeouts, and wrap the client with a Resilience4j circuit breaker plus a fallback for graceful degradation.

open as a page

When you stack @Retry, @CircuitBreaker, @RateLimiter, @TimeLimiter and @Bulkhead on one method, what is their execution order and why does it matter?

level: principalimportance: should knowfreq 33%

basics

~20 s

Resilience4j runs the aspects in a fixed nesting order (outer to inner): Retry -> CircuitBreaker -> RateLimiter -> TimeLimiter -> Bulkhead. Order matters because, e.g., Retry outside CircuitBreaker means retries are recorded by the breaker, and TimeLimiter must sit around the async call it limits.

open as a page