skip to content

What problem does CompositeCacheManager solve, and how does fallbackToNoOpCache interact with unknown cache names?

level: seniorimportance: should knowfreq 30%

answer

  1. Ordered delegates, first non-null wins
  2. Mix Redis + Caffeine by name
  3. fallbackToNoOp appends NoOpCacheManager
  4. unknown name => null => 'Cannot find cache' error
  5. dynamic-create manager must go last

basics

~20 s

CompositeCacheManager delegates to an ordered list of CacheManagers and returns the first that owns a requested cache name, letting you mix providers. Enabling setFallbackToNoOpCache appends a NoOpCacheManager so unknown names resolve to a no-op cache instead of throwing.

solid answer

~40 s

CompositeCacheManager lets one application use several providers at once — for example Redis for shared caches and Caffeine for hot local ones. It holds an ordered list of delegate CacheManagers; on getCache(name) it asks each in order and returns the first non-null Cache. If no delegate knows the name, getCache returns null, and the cache interceptor then raises an error like 'Cannot find cache named X'. Setting setFallbackToNoOpCache(true) appends a NoOpCacheManager as the last delegate, so any otherwise-unknown name resolves to a no-op cache (every get is a miss) rather than failing. This is useful when some annotated caches are optional or environment-specific. Order matters: the first delegate that claims a name wins, so put more specific managers first. For finer, per-operation routing you'd instead use a CacheResolver.

code

java · 8 lines
java
@Bean
public CacheManager cacheManager(RedisCacheManager redis, CaffeineCacheManager caffeine) {
    CompositeCacheManager composite = new CompositeCacheManager(redis, caffeine);
    // Any name not owned by redis or caffeine resolves to a no-op cache
    // instead of throwing 'Cannot find cache named ...':
    composite.setFallbackToNoOpCache(true);
    return composite;
}

go deeper

for a junior

Know it delegates to a list of managers and returns the first that has the cache.

for a middle

Explain fallbackToNoOpCache and the unknown-name error it prevents.

for a senior

Reason about ordering, dynamic-create pitfalls, and name-based vs dynamic routing.

for a principal

Weigh graceful degradation vs failing loud on config errors and decide composite vs CacheResolver for multi-tenant routing.

## Why CompositeCacheManager exists A single `CacheManager` bean normally backs every `@Cacheable`. But real systems sometimes want **different stores for different cache names** — e.g. a distributed `RedisCacheManager` for data shared across instances and a fast in-JVM `CaffeineCacheManager` for local, high-churn lookups. `org.springframework.cache.support.CompositeCacheManager` composes these. ## How resolution works - It wraps an ordered `List<CacheManager>` (via constructor varargs or `setCacheManagers(...)`). - `getCache(name)` iterates the delegates **in order** and returns the **first non-null** `Cache`. So if two delegates both define a cache with the same name, the earlier delegate wins — ordering is significant, and you should place more specific/authoritative managers first. - `getCacheNames()` is the union across delegates. ## The unknown-name problem and fallbackToNoOpCache If **no** delegate owns the requested name, `getCache` returns `null`. The caching interceptor (`CacheAspectSupport`) treats a null cache for a declared operation as a configuration error and throws (message akin to 'Cannot find cache named ...'). That is the correct strict behaviour, but sometimes a cache is optional or only present in certain environments. `setFallbackToNoOpCache(true)` **appends a `NoOpCacheManager` as the final delegate**. Because a `NoOpCacheManager` returns a (no-op) cache for *any* name, the composite now never returns null: unknown names resolve to a `NoOpCache` where every `get` is a miss and `put`/`evict` do nothing. Net effect: the annotated method just runs every time, uncached, instead of the app failing. This makes optional caches degrade gracefully. ## Trade-offs and gotchas - **Silent typos**: with fallback enabled, a misspelled cache name silently becomes a no-op cache, hiding the mistake. Without fallback you get a loud startup/first-use error. Choose per your tolerance. - **Ordering bugs**: if a broad manager (dynamic-create) is placed first, it may claim names you intended for a later, specialized manager. Managers that create caches on demand (like a default `ConcurrentMapCacheManager` in dynamic mode) will answer for *any* name and short-circuit the list — put them last or disable dynamic creation. - **Composite is name-based only**: it cannot route by method arguments, tenant, or runtime context. For that you need a `CacheResolver`. - The composite itself has no eviction/TTL; policies belong to each delegate. ## When to use what - **CompositeCacheManager**: static, name-partitioned mix of providers, optionally with graceful degradation via NoOp fallback. - **CacheResolver**: dynamic, per-invocation selection (e.g., pick a cache by tenant or argument).

  • Why can placing a dynamic ConcurrentMapCacheManager first break a CompositeCacheManager?
    In dynamic mode it creates and returns a cache for any name, so it always answers first and short-circuits later, more specific delegates. Put it last or fix its cache names.
  • What's the downside of enabling fallbackToNoOpCache?
    Misspelled or missing cache names silently degrade to a no-op cache instead of raising a clear error, so configuration mistakes go unnoticed.

saying these in an interview costs you the question

  • Thinking CompositeCacheManager merges entries or replicates across delegates (it only routes by name)
  • Believing it can route by method arguments or tenant (that's CacheResolver)
  • Assuming unknown names are silently ignored by default (they throw unless fallbackToNoOp is set)

context