Fallback Pattern
Providing a degraded but functional alternative when the primary path fails.
What it is
The Fallback pattern defines an alternative execution path that activates when the primary path fails — whether due to an exception, a circuit breaker opening, a bulkhead rejection, or a timeout. The fallback does not fix the failure; instead, it provides a partial, degraded, or default response that allows the overall request to complete with reduced functionality rather than failing entirely. Fallbacks range from returning a cached last-known-good response, to calling a simpler secondary service, to returning a static default value or an empty list.
In Resilience4j, a fallback function is registered on a decorated callable; when any exception type in a configured list is thrown, the framework routes the call to the fallback instead of propagating the exception. The fallback itself should be simple, fast, and side-effect-free — it must not make additional network calls that could themselves fail. A chained fallback can call a secondary cache, but that chain should be shallow and have its own timeout to prevent compounding failure depth.
Why it exists
Users experience a total error far more negatively than they experience reduced functionality. An e-commerce product page that shows "Recommendations unavailable" is substantially better than a 500 error that prevents the page from loading at all. The Fallback pattern enables this partial availability by explicitly modelling the degraded state as a first-class outcome rather than treating it as an exceptional edge case. Teams that design fallbacks upfront — as part of their service contract — tend to build more resilient systems because they are forced to reason about which features are genuinely critical and which can be omitted under pressure.
When to use
- Non-critical features (recommendations, social feeds, analytics widgets) where an empty or default response is acceptable.
- Read operations where cached data from minutes or hours ago is still useful (product prices, user profiles, configuration).
- Operations backed by a secondary system (hot standby, read replica, CDN-cached static resource) that can be substituted for the primary.
- External API calls where a sensible default exists (e.g., defaulting to standard shipping when a rates API is down).
- Any call path where the cost of total failure is substantially higher than the cost of serving slightly stale or incomplete data.
When not to use
- Write operations where there is no safe default — silently discarding a write is almost always wrong; fail explicitly instead.
- Operations where a wrong default is more harmful than an error (e.g., defaulting to "user is authorised" when the authorisation service is down).
- When the fallback itself makes external calls without its own circuit breaker — a fallback that can fail adds a second layer of failure to handle.
- As a substitute for fixing the underlying reliability problem — high fallback activation rates are a signal the primary path needs work, not that the fallback is a permanent solution.
Typical architecture
Fallback Decision Tree
═══════════════════════════════════════════════════════
Request for Product Recommendations
│
▼
┌─────────────────────────┐
│ Call Recommendation │
│ Service (primary) │
└───────────┬─────────────┘
│
┌────────┴──────────┐
│ Success? │ Failed / Circuit Open / Timeout
▼ ▼
Return results ┌─────────────────────────────┐
│ Fallback Chain │
│ │
│ 1. Redis cache │
│ (last 30 min results) │
│ │ miss │
│ ▼ │
│ 2. Pre-computed static │
│ "popular items" list │
│ │ not available │
│ ▼ │
│ 3. Return empty list [] │
│ + UI shows "No recs" │
└─────────────────────────────┘
Staleness TTL: cached fallback is used if age < 30 min.
Monitor: fallback_activations_total metric + alert if > 5%
Pros and cons
Pros
- Converts hard failures into soft degradation, dramatically improving user-visible availability.
- Forces architects to explicitly classify which features are critical vs. non-critical during design time.
- Works naturally with circuit breakers — the circuit opens and the fallback activates without any request reaching the failing downstream.
- Cached fallbacks can actually improve latency compared to the primary path during normal operation.
- Enables progressive enhancement: the primary path provides rich functionality; the fallback provides basic functionality.
Cons
- Stale fallback data can mislead users — a product shown as "in stock" from a cached response when it is actually out of stock.
- Adds code complexity — every primary call needs a corresponding fallback implementation, tested independently.
- High fallback activation rates can mask a deeper reliability problem that needs fixing.
- Fallback chains can become complex and themselves become a maintenance burden.
Implementation notes
Fallback staleness is one of the most important design decisions. Define a Time-To-Live for each cached fallback based on how rapidly the data changes and how harmful stale data is. Product prices might tolerate a 5-minute cache; user permissions should not use a cached fallback at all. Track the age of fallback data and include it in the response metadata or as a UI indicator so downstream consumers and users can make informed decisions.
Monitor the fallback activation rate as a separate metric (fallback_activations_total) and alert when it exceeds a baseline threshold (e.g., 2% of requests). A sudden spike in fallback activation is often the first indicator of a new dependency problem before circuit breakers trip. Log the specific exception type that triggered each fallback activation to enable rapid root cause identification.
Common failure modes
- Fallback makes network calls: the fallback calls another service that can also fail, creating a nested failure scenario that is harder to debug and recover from.
- No staleness TTL: a cached fallback returns data that is hours or days old, misleading users with severely outdated information.
- Silent fallback without logging: fallbacks activate quietly with no metrics or alerts, and the team has no visibility into a degrading dependency.
- Fallback for write operations: a write fallback silently drops the write, causing data loss that the user believes has been persisted.
- Over-fallback: fallbacks are applied so broadly that users routinely see degraded state even when the primary path is healthy, normalising partial functionality.
Decision checklist
- Is there a clearly defined fallback response for every protected call path that can fail?
- Is the fallback free of network calls or other external dependencies that could themselves fail?
- Is a staleness TTL defined for any cached fallback data?
- Is fallback activation rate tracked as a metric and alerted on?
- Are write operations excluded from fallback policies (or fail-fast instead of silently dropping writes)?
- Have you verified that the fallback is itself tested under the same conditions that would activate it?
Example use cases
- A product page API returning a cached list of recommendations from Redis when the ML recommendation service circuit breaker opens.
- A shipping rate calculator returning "standard shipping: $5.99" as a static default when the carrier rate API times out during checkout.
- A user profile service returning a minimal profile (name, email only) from a local cache when the extended profile service is unavailable.
- A feature flag service returning hardcoded "off" values for all flags when the feature flag provider is unreachable, ensuring safe default behaviour.
Related patterns
- Circuit Breaker — the trigger that activates the fallback when a dependency is persistently failing.
- Graceful Degradation — the broader strategy of which fallbacks are a specific implementation mechanism.
- Bulkhead Pattern — a bulkhead rejection also triggers the fallback path.
Further reading
- Resilience4j Fallback documentation — fallback functions with Resilience4j circuit breaker and bulkhead integration.
- Retry pattern — Azure Architecture Center — covers retry with fallback combinations.