Spring Cache
Quick Start
@SpringBootApplication @EnableCaching public class Application {}
@Service public class UserService {
@Cacheable("users")
public User findById(Long id) {
return userRepository.findById(id).orElseThrow();
}
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
spring: cache: type: caffeine caffeine: spec: maximumSize=1000,expireAfterWrite=10m
Cache Annotations
@Cacheable
@Cacheable("products") public Product findById(Long id) { }
@Cacheable(value = "products", key = "#category + '-' + #status") public List<Product> findByCategoryAndStatus(String category, String status) { }
@Cacheable(value = "products", condition = "#id > 0") public Product findByIdConditional(Long id) { }
@Cacheable(value = "products", unless = "#result == null") public Product findByIdUnlessNull(Long id) { }
@Cacheable(value = "products", sync = true) // One thread populates public Product findByIdSync(Long id) { }
@CacheEvict
@CacheEvict(value = "products", key = "#id") public void deleteProduct(Long id) { }
@CacheEvict(value = "products", allEntries = true) public void clearProductCache() { }
@CacheEvict(value = "products", key = "#id", beforeInvocation = true) public void deleteProductBeforeInvocation(Long id) { }
@CachePut
@CachePut(value = "products", key = "#product.id") public Product saveProduct(Product product) { return productRepository.save(product); }
@CachePut(value = "products", key = "#result.id") public Product createProduct(CreateProductRequest request) { return productRepository.save(new Product(request)); }
@Caching (Multiple Operations)
@Caching( put = { @CachePut(value = "products", key = "#result.id"), @CachePut(value = "productsBySku", key = "#result.sku") }, evict = { @CacheEvict(value = "productList", allEntries = true) } ) public Product createProduct(CreateProductRequest request) { }
@CacheConfig (Class-Level)
@Service @CacheConfig(cacheNames = "products", keyGenerator = "customKeyGenerator") public class ProductService {
@Cacheable // Uses class config
public Product findById(Long id) { }
@Cacheable(cacheNames = "inventory") // Override cache name
public Inventory getInventory(Long productId) { }
}
Full Reference: See managers.md for Caffeine, Redis, EhCache configurations.
Quick Cache Manager Setup
Caffeine (Single Instance)
@Bean public CacheManager cacheManager() { CaffeineCacheManager manager = new CaffeineCacheManager(); manager.setCaffeine(Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(Duration.ofMinutes(10)) .recordStats()); return manager; }
Redis (Distributed)
@Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(10)) .serializeValuesWith(SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
Full Reference: See advanced.md for Multi-Level Caching, Metrics, Synchronization.
Best Practices
Do Don't
Use Caffeine for single-instance Skip TTL configuration
Use Redis for distributed Cache mutable objects
Configure TTL always Ignore cache eviction
Use sync=true for expensive ops Use high cardinality keys
Implement cache metrics Cache sensitive data unencrypted
Production Checklist
-
Cache provider configured (Caffeine/Redis)
-
TTL configured for every cache
-
Cache eviction on write operations
-
Metrics configured
-
Serialization tested
-
Distributed lock for critical ops
When NOT to Use This Skill
-
Distributed caching - Use spring-data-redis
-
Redis operations - Use redis skill
-
Session storage - Use Spring Session
Common Pitfalls
Error Cause Solution
Cache not working Internal call (same bean) Use self-injection
Null pointer Null values cached Use unless = "#result == null"
Memory leak TTL not configured Set expireAfterWrite
Serialization error Non-serializable objects Implement Serializable
Anti-Patterns
Anti-Pattern Problem Solution
Caching mutable objects Stale data Cache immutable data
No TTL configured Stale cache forever Set expireAfterWrite
@Cacheable on void No effect Only cache with return
No cache sync Race conditions Use sync=true or locks
Quick Troubleshooting
Problem Diagnostic Fix
Cache not working Check @EnableCaching Add annotation
Wrong data cached Check cache key Define explicit key
Cache not evicted Check key expression Verify key matches
Self-invocation bypass Same class call Inject self
Reference Files
File Content
managers.md Caffeine, Redis, EhCache, Key Generators
advanced.md Multi-Level, Metrics, Sync, Testing
External Documentation
-
Spring Cache Abstraction
-
Spring Boot Caching
-
Caffeine