Files
spring-boot-demo/caching/docs/08-providers-and-ttl.md
T
asmhatre 66208bcd97 Add caching: the Spring cache abstraction, keys, eviction timing and the self-invocation trap
A Spring Boot 4.1.1 module whose test suite is the evidence for the article: 19 tests
producing 22 transcripts under docs/output/, plus 12 documentation chapters.

Findings the build pins:
- @EnableCaching has no exposeProxy attribute; the widely-copied
  @EnableCaching(exposeProxy = true) does not compile.
- Two methods sharing a cache name and an argument type share a key space, and one
  silently serves the other's answers.
- beforeInvocation = true is NOT deferred by TransactionAwareCacheManagerProxy on
  7.0.9 - doEvict picks evictIfPresent, which the decorator does not intercept.
- Four of five invalid declarations start a clean context and throw at the first call.
- Caffeine on the classpath silently displaces the simple provider.
2026-09-12 05:19:22 +00:00

3.7 KiB

← sync and async · next: versus Hibernate L2 →

8. Providers, TTL, and the dependency that changes your cache

The abstraction has no TTL

None. No time-to-live, no time-to-idle, no maximum size, no eviction policy. Those are provider features, and the reference documentation says so plainly.

On the default simple provider — a ConcurrentHashMap behind ConcurrentMapCache — an entry stays until something evicts it by hand or the process ends. That is fine for a lookup table with twelve rows and a slow leak for anything else.

With Caffeine configured for expireAfterWrite=400ms, maximumSize=3, docs/output/15-providers-and-ttl.txt:

two calls, same key, immediately -> 1 repository calls
one more call 600 ms later      -> 2 repository calls   <- the entry expired

five distinct keys written, maximumSize = 3
estimated size after eviction settles : 3
stats                                 : hits=1 misses=5 evictions=3

Auto-detection, and why your provider changed

If there is no CacheManager bean and no cacheResolver, Spring Boot walks a fixed order and stops at the first provider on the classpath:

1 Generic   2 JCache   3 Hazelcast   4 Infinispan   5 Couchbase
6 Redis     7 Caffeine   8 Cache2k   9 Simple

That has a consequence worth internalising. This module added Caffeine because one chapter needed TTL. Every cache in the application moved off the simple provider as a result, and nothing said so — docs/output/18-provider-detection.txt catches it:

spring.cache.type          : (not set)
resolved CacheManager bean : org.springframework.cache.caffeine.CaffeineCacheManager

A transitive dependency on Hazelcast, added for something unrelated, will do the same thing and outrank Redis while it is at it. Set spring.cache.type explicitly in anything you deploy.

Per-provider configuration

Property Effect
spring.cache.type generic, jcache, hazelcast, infinispan, couchbase, redis, caffeine, cache2k, simple, none
spring.cache.cache-names Creates exactly these caches at startup; anything else fails at the call
spring.cache.caffeine.spec e.g. maximumSize=500,expireAfterAccess=600s
spring.cache.redis.time-to-live a Duration
spring.cache.redis.cache-null-values default true
spring.cache.redis.key-prefix, use-key-prefix keyspace hygiene in a shared Redis
spring.cache.jcache.provider, spring.cache.jcache.config JSR-107

spring.cache.type=none gives a NoOpCacheManager: every method runs every time, and the annotations stay where they are. Useful in tests, and the fastest way to answer "is the cache causing this?".

spring.cache.cache-names is worth using in production: it turns a typo in a cache name from a cache that silently never hits into an IllegalArgumentException at the first call.

One caveat — a single spring.cache.caffeine.spec applies to every cache. Per-cache expiry needs your own CaffeineCacheManager (or several CacheManager beans and cacheManager = "..." on the operations), which is another argument for one cache name per method.

Where the auto-configuration lives in Boot 4

org.springframework.boot.cache.autoconfigure.CacheAutoConfiguration, shipped in the spring-boot-cache module. Boot 4 split spring-boot-autoconfigure into per-technology modules; the Boot 3 coordinate org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration no longer resolves. docs/output/16-autoconfiguration.txt checks both names against the running classpath. It matters if you write @ImportAutoConfiguration or exclusions by class name.