Files
spring-boot-demo/caching/docs/12-production-checklist.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

2.7 KiB

← diagnostics · README

12. Before this goes to production

Should there be a cache here at all?

Be honest about the answer. A cache is a correctness liability you accept in exchange for latency. If the method is not measurably slow, or the hit rate will be low, or the data must be current, the right amount of caching is none. Half the caches in a typical codebase were added without a measurement and are never revisited.

The checklist

Configuration

  • spring.cache.type is set explicitly, so a new dependency cannot change the provider (chapter 8)
  • spring.cache.cache-names declares every cache, so a typo fails loudly
  • Every mutable cache has a TTL. It bounds the damage from every other mistake on this list
  • Every cache has a size bound, or the data set is provably small
  • @EnableCaching is not on the main application class

Correctness

  • One cache name per method, or an explicit key that includes the method (chapter 4)
  • Keys are immutable and serialise to something stable
  • Cached values are DTOs, not JPA entities (chapter 9)
  • Cached values are immutable, or defensively copied — the map hands every caller the same instance
  • Mutating methods evict rather than put, with beforeInvocation = true (chapter 5)
  • Nothing relies on this.cachedMethod(...) (chapter 3)
  • Every cached method is called at least once by a test — four of the five invalid declarations in chapter 2 only fail at the first call

Operations

  • Hit rate and eviction count are on a dashboard (recordStats() for Caffeine)
  • There is a way to clear a cache without a deployment (DELETE /actuator/caches/{name})
  • A CacheErrorHandler decision has been made per cache, not inherited by accident (chapter 5)
  • The behaviour with spring.cache.type=none has been tried at least once

Distributed caches only

  • Values are serializable and the format survives a rolling deployment — a changed DTO shape with old entries still in Redis fails on read, per instance, at whatever hour
  • spring.cache.redis.key-prefix keeps this application out of everyone else's keyspace
  • The failure mode when the cache is unreachable has been decided: degrade or fail
  • TTLs are short enough that a missed eviction self-corrects

The one-line version

Give every cache a TTL, evict rather than put, cache DTOs, and set spring.cache.type. Those four cover most of what goes wrong.