Reliability & Resilience Resilience

Fallback Pattern

Providing a degraded but functional alternative when the primary path fails.

⏱ 8 min read

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