What problem does CompositeCacheManager solve, and how does fallbackToNoOpCache interact with unknown cache names?
answer
- Ordered delegates, first non-null wins
- Mix Redis + Caffeine by name
- fallbackToNoOp appends NoOpCacheManager
- unknown name => null => 'Cannot find cache' error
- dynamic-create manager must go last
basics
~20 sCompositeCacheManager 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 sCompositeCacheManager 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@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
Know it delegates to a list of managers and returns the first that has the cache.
Explain fallbackToNoOpCache and the unknown-name error it prevents.
Reason about ordering, dynamic-create pitfalls, and name-based vs dynamic routing.
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)