Files
spring-boot-demo/caching/docs/02-the-three-annotations.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

← what caching is · next: self-invocation →

2. The annotations, attribute by attribute

Verified against spring-context 7.0.9 with javap, so this is what the class files declare rather than what the documentation summarises.

@Cacheable

Attribute Type Notes
value / cacheNames String[] Aliases. Several names means several caches are consulted and all of them written.
key String SpEL. Mutually exclusive with keyGenerator; setting both fails the context at startup.
keyGenerator String Bean name of a KeyGenerator.
cacheManager String Bean name, for when there is more than one.
cacheResolver String Full control over which caches this operation uses. Mutually exclusive with cacheManager.
condition String SpEL, evaluated before the call. False means no lookup and no write.
unless String SpEL, evaluated after. Can see #result. Vetoes the write only.
sync boolean One caller computes, the rest wait. Heavily restricted — see chapter 7.

@CachePut

Same attributes minus sync. Always invokes the method, always writes the result. Use it when you already have the new value and want to avoid the miss that an eviction would cause.

Do not put @CachePut and @Cacheable on the same method. The framework does not stop you, and the two have opposite intentions.

@CacheEvict

Same as @Cacheable minus unless and sync, plus:

Attribute Type Notes
allEntries boolean Clears the whole region in one operation instead of key by key.
beforeInvocation boolean Default false — evict after a successful return. See chapter 5.

void is fine here; the annotation is a trigger and the return value is ignored.

@Caching

A container for several operations of the same type on one method:

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames = "secondary", key = "#p0") })
public Book importBooks(String deposit, Date date) { ... }

@CacheConfig

Class-level defaults for cacheNames, keyGenerator, cacheManager and cacheResolver. It enables nothing on its own. Precedence runs global (CachingConfigurer) → class (@CacheConfig) → operation, with the operation always winning.

@EnableCaching

Exactly three attributes, and this is worth knowing because a popular piece of advice uses a fourth that does not exist:

AdviceMode mode()
int order()
boolean proxyTargetClass()

There is no exposeProxy. @EnableCaching(exposeProxy = true) does not compile. docs/output/04-non-public-and-postconstruct.txt prints the reflected attribute list.

Spring Boot's reference documentation advises against putting @EnableCaching on the main application class, because it makes caching mandatory for every test slice too. Put it on a @Configuration class you can exclude.

Declarations that are rejected, and when

docs/output/20-invalid-declarations.txt runs five bad declarations through a real context. Only the first fails at startup:

Declaration Fails
key and keyGenerator together at startup
sync = true with unless at the first call
sync = true across two caches at the first call
sync = true combined with another cache operation at the first call
a cache name not in spring.cache.cache-names at the first call

Four of the five start a healthy-looking application and throw on a code path that may not run for hours. That is the single strongest argument for having a test that actually calls each cached method.