What are the built-in CacheManager implementations Spring ships, and when would you use each?
answer
- ConcurrentMap = tests, no eviction
- Caffeine = prod local, TTL/size
- NoOp = disable without removing annotations
- Composite = ordered delegates, fallbackToNoOp
- JCache = JSR-107 Ehcache3/Hazelcast
basics
~10 sSpring provides ConcurrentMapCacheManager (in-memory maps, good for tests), CaffeineCacheManager (Caffeine with eviction/TTL, common in prod), NoOpCacheManager (disables caching), CompositeCacheManager (combines several), and JCacheCacheManager (JSR-107 providers).
solid answer
~40 sThe cache abstraction needs a CacheManager bean. ConcurrentMapCacheManager stores entries in ConcurrentHashMaps — zero deps, no eviction or TTL, ideal for tests or trivial caches. CaffeineCacheManager wraps the Caffeine library, giving size/time eviction and stats; it is the usual local-cache production choice. NoOpCacheManager hands back caches that never store anything (every get is a miss), letting you disable caching without stripping @Cacheable annotations. CompositeCacheManager delegates to an ordered list of managers, returning the first that owns a named cache. JCacheCacheManager adapts a JSR-107 (javax.cache) provider such as Ehcache 3 or Hazelcast. For distributed caching you'd use RedisCacheManager (Spring Data Redis). You pick based on whether you need eviction, distribution, or a standardized provider.
code
java · 21 lines@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager mgr = new CaffeineCacheManager("users", "catalog");
mgr.setCaffeine(Caffeine.newBuilder()
.maximumSize(1_000)
.expireAfterWrite(Duration.ofMinutes(10))
.recordStats());
return mgr;
}
// Swap in for a profile to turn caching off without touching @Cacheable methods
@Bean
@Profile("no-cache")
public CacheManager noOpCacheManager() {
return new NoOpCacheManager();
}
}go deeper
Know the names and one-line purpose of each built-in CacheManager.
Explain eviction differences and configuring Caffeine via spec strings.
Discuss dynamic vs fixed cache names, null-value handling, and choosing distributed managers.
Reason about routing multiple providers with CompositeCacheManager and per-operation manager selection across a modular system.
## The role of CacheManager Spring's cache abstraction (`@EnableCaching` + `@Cacheable`/`@CacheEvict`/`@CachePut`) is provider-agnostic. The bridge to an actual store is a single bean of type `org.springframework.cache.CacheManager`. A `CacheManager` is a factory/registry of named `org.springframework.cache.Cache` instances (`getCache(name)`, `getCacheNames()`). Auto-configuration or your `@Configuration` supplies exactly one primary `CacheManager`. ## The built-in implementations ### ConcurrentMapCacheManager Backed by a `java.util.concurrent.ConcurrentHashMap` per cache. **No eviction, no TTL, no max size, no statistics** — entries live until explicitly evicted or the JVM restarts. By default it creates caches lazily on first request (dynamic mode) and allows null values (`setAllowNullValues(true)` by default). Restrict to fixed names with `setCacheNames(...)`, which also switches off dynamic creation. Use for unit/integration tests or tiny, bounded lookup tables. Never use where unbounded growth is a memory-leak risk. ### CaffeineCacheManager Wraps the Caffeine library (`com.github.ben-manes.caffeine`). Supports **eviction by maximum size, time-to-live/time-to-idle, weak/soft references, and hit/miss statistics**. Configure once and apply to all caches via `setCaffeine(Caffeine.newBuilder()...)`, `setCaffeineSpec(...)`, or a string `setCacheSpecification("maximumSize=500,expireAfterWrite=10m")`. It also supports an async mode (`setAsyncCacheMode(true)`) for use with `CompletableFuture`/reactive return types. This is the default local, in-process production cache in modern Spring apps (Guava's cache is deprecated in Spring's support). ### NoOpCacheManager Returns `NoOpCache` instances: `get` always returns `null` (a guaranteed miss, so the target method always runs) and `put`/`evict` do nothing. It lets you **disable caching globally without removing annotations** — handy for a profile or troubleshooting whether caching hides a bug. It never throws for unknown cache names. ### CompositeCacheManager Holds an ordered `List<CacheManager>` and, for a given cache name, returns the `Cache` from the **first delegate that knows it**. Lets you mix providers (e.g., a Redis manager for some names, Caffeine for others). `setFallbackToNoOpCache(true)` appends a `NoOpCacheManager` so unknown names resolve to a no-op cache instead of `null` (which would otherwise raise 'Cannot find cache' errors). ### JCacheCacheManager (JSR-107) Adapts a standardized `javax.cache.CacheManager` from a JSR-107 provider (Ehcache 3, Hazelcast, Infinispan, etc.). You configure the provider (often via a `cache.xml`/config URI) and Spring wraps each `javax.cache.Cache`. Use when you want a vendor-neutral JCache provider or already standardize on JSR-107. ## Beyond the core module `RedisCacheManager` (Spring Data Redis) is the common distributed choice; other Spring projects add their own. These aren't in spring-context core but plug into the same abstraction. ## Gotchas - Exactly one `CacheManager` is used unless you route with `CompositeCacheManager` or per-operation `cacheManager`/`cacheResolver` attributes. - `ConcurrentMapCacheManager` growth is unbounded — a frequent production incident. - Null-value handling differs; if the store can't hold null, disable `allowNullValues` or the manager serializes a placeholder. - Dynamic vs fixed cache names: fixing names catches typos in `@Cacheable("typ0")` at startup/first-use instead of silently creating a cache.
- Why is ConcurrentMapCacheManager risky in production?It has no eviction, TTL, or max size, so caches grow without bound — a memory leak under high-cardinality keys. It also lacks statistics for diagnosing hit rates.
- How do you disable caching temporarily without editing every annotated method?Register a NoOpCacheManager (e.g., under a profile). Every get becomes a miss so target methods always run, and puts/evicts are ignored — annotations stay in place.
saying these in an interview costs you the question
- Claiming ConcurrentMapCacheManager supports TTL/eviction
- Thinking NoOpCacheManager throws or blocks annotated methods (it just makes every get a miss)
- Saying you must delete @Cacheable to turn caching off