Caching with @Cacheable
@Cacheable moves cache-aside boilerplate into declarative annotations — Spring Cache abstraction backed by RedisCacheManager stores method results keyed by SpEL expression.
Introduction
@Cacheable moves cache-aside boilerplate into declarative annotations — Spring Cache abstraction backed by RedisCacheManager stores method results keyed by SpEL expression. Amazon product services cache getProductByAsin behind @Cacheable("products").
Configure TTL per cache name, disable caching in tests with @Profile, and always plan eviction on @CacheEvict when data mutates. sync=true on @Cacheable prevents stampede on hot keys.
Declarative cache is not magic — wrong key expression caches collisions; null results need unless="#result == null" handling.
Understanding the topic
Key concepts
- @EnableCaching on configuration class.
- RedisCacheManager + CacheConfiguration TTL.
- @Cacheable cacheNames key SpEL.
- @CacheEvict on updates/deletes.
- @CachePut force update cache.
- sync=true — single-flight on miss.
flowchart LRApp -->|GET key| RedisRedis -->|miss| DBDB -->|populate| RedisRedis --> App
Step-by-step explanation
- AOP interceptor wraps @Cacheable method.
- Compute cache key from name + SpEL.
- Redis GET — hit returns without method call.
- Miss executes method; SET result with TTL.
- Evict removes key on mutation.
Syntax reference
Common commands
- unless skips caching null.
- sync=true expensive but stops stampede.
- Custom RedisCacheConfiguration per cache name TTL.
@Cacheable(cacheNames = "products", key = "#sku", unless = "#result == null")public Product findBySku(String sku) { ... }@CacheEvict(cacheNames = "products", key = "#sku")public void updateProduct(String sku, Product p) { ... }
Informative example
Product service with cache and eviction — Spring Boot 3:
@Servicepublic class ProductService {@Cacheable(cacheNames = "catalog", key = "#sku", sync = true)public Product getBySku(String sku) {return repository.findBySku(sku).orElseThrow();}@CacheEvict(cacheNames = "catalog", key = "#sku")public void invalidate(String sku) {repository.save(/* updated */);}}
Register RedisCacheManager bean with Duration.ofMinutes(30) for catalog cache. Enable caching in @Configuration.
Real-world use
Real-world use cases
- Read-heavy repository methods.
- Expensive aggregation computed once per TTL.
- Reference data (countries, tax rates).
- GraphQL DataLoader backend cache.
- API gateway response cache layer.
Best practices
- TTL per cache volatility.
- Evict on every write path.
- sync=true on viral hot keys only.
- Integration test with @CacheEvict clear.
- Monitor hit rate via custom metrics.
- Document SpEL key scheme.
Common mistakes
- Cache without eviction — stale forever until TTL.
- Caching entities with lazy JPA collections — serialization pain.
- Same cache name different key shapes.
- Caching mutable objects returned by reference.
Advanced interview questions
Q1Beginner@Cacheable behavior?
Q2Beginner@CacheEvict when?
Q3Intermediatesync=true purpose?
Q4IntermediateCache null results?
Q5Advanced@Cacheable vs manual RedisTemplate?
Summary
@Cacheable = declarative cache-aside.