Files
spring-boot-demo/caching
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
..

caching

Companion project for The Spring Cache Abstraction: @Cacheable, @CacheEvict, Key Generators and the Self-Invocation Trap on ankurm.com.

Every number, key dump, stack trace and error message quoted in the article came out of docs/output/, and every one of those files is regenerated by one script. Most of them are produced by the test suite, so if a claim stops being true the build goes red.

Versions

Spring Boot 4.1.1
Spring Framework 7.0.9
Caffeine managed by Boot
JDK 25 (Temurin 25.0.4.1+1)
Maven 3.9

Quickstart

export JAVA_HOME=/path/to/jdk-25
mvn -DskipTests package
./scripts/run-all.sh          # regenerates every file under docs/output/

Run the application on its own to poke at the diagnostics endpoint:

java -jar target/caching-1.0.0.jar --spring.cache.type=simple
curl -s localhost:8080/diag/warm
curl -s localhost:8080/diag/caches | jq .

Profiles

Profile What it changes
(none) Boot's auto-detected provider. Caffeine is on the classpath, so that is what you get
caffeine An explicit CaffeineCacheManager with expireAfterWrite=400ms, maximumSize=3, recordStats() and setAsyncCacheMode(true)
txaware TransactionAwareCacheManagerProxy over ConcurrentMapCacheManager

Most tests pin spring.cache.type=simple so the key dumps show a real ConcurrentHashMap.

Endpoints

Endpoint Purpose
GET /diag/warm Calls a few cached methods so there is something to look at
GET /diag/caches Every cache, every key, with the runtime class of key and value
GET /actuator/caches Boot's own view: cache names and their manager
GET /actuator/metrics/cache.gets Hit/miss counters, where the provider reports them

/diag/* is a debugging aid with no authorisation. Delete it before shipping anything.

Documentation

Chapter Covers
01 — What the cache abstraction is the interceptor and the Cache interface; when not to cache
02 — The annotations, attribute by attribute every attribute from javap; five declarations that are rejected and when
03 — The self-invocation trap why this.method() caches nothing, three fixes ranked, non-public methods, @PostConstruct
04 — Keys SimpleKeyGenerator's three rules, the collision it makes easy, the SpEL surface, mutable keys
05 — Eviction beforeInvocation, what a thrown exception does, allEntries, CacheErrorHandler
06 — Conditions and nulls condition vs unless, NullValue, cache penetration
07 — sync and async the stampede measured, the four sync=true restrictions, CompletableFuture
08 — Providers and TTL detection order, the dependency that silently changes your provider, per-provider properties
09 — This is not the Hibernate L2 cache three layers compared; why caching an entity gives you a shared detached object
10 — Transactions a rollback that does not roll the cache back; TransactionAwareCacheManagerProxy and a correction to a widely repeated claim
11 — Diagnostics four ways to see what the cache is doing
12 — Before production the checklist, and whether to cache at all

Captured output

File What it shows
01-basics.txt 200 ms, then 0 ms, with the invocation counter
02-put-evict-clear.txt the three annotations against one cache
03-self-invocation.txt 4 vs 2 repository calls, four call styles
04-non-public-and-postconstruct.txt a protected method silently uncached; @EnableCaching's real attributes
05-key-shapes.txt zero, one and two arguments in one map
06-key-collision.txt one method serving another method's answer
07-mutable-key.txt two entries, one unreachable
08-evict-timing.txt a failed update leaving a stale entry
09-conditions.txt condition vetoing before the call
10-nulls.txt NullValue in the map
11-stampede.txt 16 invocations vs 1
12-async-return-types.txt CompletableFuture with async cache mode on
13-transactions.txt the cache keeping a rolled-back value
14-cached-entity.txt LazyInitializationException from a cached entity
15-providers-and-ttl.txt Caffeine expiry, size bound and stats
16-autoconfiguration.txt what Boot wired, and where the auto-configuration lives in Boot 4
17-async-cache-mode-missing.txt the runtime error a clean startup hides
18-provider-detection.txt the provider nobody chose
19-transaction-aware.txt the deferred put, and the evict that is not deferred
20-invalid-declarations.txt five rejected declarations, four of them at the first call
22-decorator-bytecode.txt javap proving why beforeInvocation is not deferred
23-diagnostics.txt every cache, every key, live

Licence

MIT — see LICENSE.