skip to content

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

level: middleimportance: must knowfreq 65%

answer

  1. fallback = canned impl of the interface, no cause
  2. fallbackFactory = FallbackFactory<T>.create(Throwable)
  3. enable spring.cloud.openfeign.circuitbreaker.enabled
  4. both must be Spring beans
  5. factory preferred — keeps the cause

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.

solid answer

~40 s

On `@FeignClient` you can set `fallback = SomeImpl.class` or `fallbackFactory = SomeFactory.class`. Both require the Spring Cloud CircuitBreaker integration enabled (`spring.cloud.openfeign.circuitbreaker.enabled=true`) and the fallback registered as a Spring bean. `fallback` is a class implementing the same client interface; each method returns a static degraded value — simple but blind to *why* it failed. `fallbackFactory` implements `FallbackFactory<YourClient>`; its `create(Throwable cause)` returns a client instance, giving access to the exception. Use the factory when you must log the cause, branch behaviour (404 vs timeout vs open circuit), or include error context in the degraded response. In practice `fallbackFactory` is the recommended default because losing the cause makes production debugging painful. The fallback runs whenever the underlying circuit breaker trips or the HTTP call throws.

code

java · 21 lines
java
@FeignClient(name = "recommendations", fallbackFactory = RecoFallbackFactory.class)
public interface RecommendationClient {
    List<Item> recommend(String userId);
}

@Component
class RecoFallbackFactory implements FallbackFactory<RecommendationClient> {
    private static final Logger log = LoggerFactory.getLogger(RecoFallbackFactory.class);

    @Override
    public RecommendationClient create(Throwable cause) {
        return userId -> {
            if (cause instanceof FeignException fe && fe.status() == 404) {
                return List.of(); // genuinely no data
            }
            log.warn("reco degraded for {}: {}", userId, cause.toString());
            return List.of(Item.popularDefault()); // degraded but useful
        };
    }
}
// application.properties: spring.cloud.openfeign.circuitbreaker.enabled=true

go deeper

for a junior

Know both provide a degraded implementation of the Feign interface when calls fail.

for a middle

Explain that only fallbackFactory sees the Throwable, both must be beans, and the integration must be enabled.

for a senior

Discuss 404-as-failure trap, per-method breaker naming, and why factory is the safer default for observability.

for a principal

Set an org-wide convention (factory + structured logging + explicit 4xx handling) and reason about which endpoints deserve degradation vs fail-fast.

## Context: Feign + circuit breaker **Spring Cloud OpenFeign** turns a Java interface into an HTTP client. To make it resilient, Spring Cloud wraps each Feign method in a **Spring Cloud CircuitBreaker** (backed by Resilience4j). You must opt in: ```properties spring.cloud.openfeign.circuitbreaker.enabled=true ``` (Property name is `spring.cloud.openfeign.circuitbreaker.enabled` in recent Spring Cloud; older versions used `feign.circuitbreaker.enabled`.) Only then do `fallback`/`fallbackFactory` take effect. ## `fallback` ```java @FeignClient(name = "pricing", fallback = PricingFallback.class) public interface PricingClient { Price quote(String sku); } @Component class PricingFallback implements PricingClient { @Override public Price quote(String sku) { return Price.unavailable(sku); // static degraded value } } ``` - The fallback **implements the client interface**. - It must be a **Spring bean** (`@Component`) so Feign can find it. - Every method returns a canned degraded response. - **Limitation:** it has *no* access to the exception that caused the failure. You can't log the root cause or differentiate a 404 from a connection timeout from an OPEN circuit. ## `fallbackFactory` ```java @FeignClient(name = "pricing", fallbackFactory = PricingFallbackFactory.class) public interface PricingClient { Price quote(String sku); } @Component class PricingFallbackFactory implements FallbackFactory<PricingClient> { private static final Logger log = LoggerFactory.getLogger(PricingFallbackFactory.class); @Override public PricingClient create(Throwable cause) { return sku -> { log.warn("pricing fallback for {}: {}", sku, cause.toString()); return Price.unavailable(sku); }; } } ``` - Implements `org.springframework.cloud.openfeign.FallbackFactory<T>`. - `create(Throwable cause)` is called with the failure cause and returns an instance of the client interface. - Gives you the **cause** — you can log it, inspect `FeignException.status()`, or branch (return cached data on timeout, throw a domain exception on 4xx, etc.). - Also a **Spring bean**. ## Which to choose - **`fallbackFactory` is the pragmatic default.** The one thing plain `fallback` throws away — the cause — is exactly what you need to debug and to make smart degraded responses. - Use plain `fallback` only for trivial cases where a fixed default is fine and you don't care why it failed. ## Gotchas - **Beans, not just classes:** both must be registered in the context or startup fails / fallback is ignored. - **Integration must be enabled** — a very common 'my fallback never runs' cause is forgetting `spring.cloud.openfeign.circuitbreaker.enabled=true`. - **4xx are failures too:** by default a `FeignException` (including 404) counts as a failure and triggers the fallback; if 404 is a legitimate 'not found', handle it explicitly rather than masking it as degraded. - **You can't set both** `fallback` and `fallbackFactory` on the same client. - The circuit breaker instance name is `"YourClient#method(Type)"`, which is what you target when tuning per-method breaker config. - **Don't do heavy work in the fallback** (no second flaky remote call without its own protection). ## When to use Any `@FeignClient` calling a non-critical or enrichment endpoint where a degraded answer keeps the caller alive. Prefer the factory so failures are observable.

  • Your Feign fallback bean exists but never runs. What's the first thing you check?
    That the circuit breaker integration is enabled: `spring.cloud.openfeign.circuitbreaker.enabled=true` (older Spring Cloud: `feign.circuitbreaker.enabled=true`). Without it, `fallback`/`fallbackFactory` on `@FeignClient` are ignored. Then confirm the fallback is an actual Spring bean.
  • A downstream returns HTTP 404 for legitimately-missing resources. Why is that a fallback trap?
    Feign raises a `FeignException` on 404 by default, so the circuit breaker counts it as a failure and the fallback masks 'not found' as a degraded response — also polluting the breaker's failure rate. Use a `fallbackFactory` to inspect `FeignException.status()` and handle 404 explicitly, or configure the breaker to not record 4xx as failures.

saying these in an interview costs you the question

  • Claiming plain `fallback` gives access to the exception cause (it does not — only `fallbackFactory` does).
  • Thinking `fallback`/`fallbackFactory` work without enabling the circuit breaker integration.
  • Registering the fallback as a plain class but not a Spring bean.
  • Setting both `fallback` and `fallbackFactory` on the same client.

context