16 KiB
Spring Boot Cache Abstraction - References
Complete API reference and external resources for Spring Boot caching.
Spring Cache Abstraction API Reference
Core Interfaces
CacheManager
Interface for managing cache instances.
public interface CacheManager {
// Get a cache by name
Cache getCache(String name);
// Get all available cache names
Collection<String> getCacheNames();
}
Common Implementations:
ConcurrentMapCacheManager- In-memory, thread-safe cachingSimpleCacheManager- Simple static cache configurationCaffeineCacheManager- High-performance caching with Caffeine libraryEhCacheManager- Enterprise caching with EhCacheRedisCacheManager- Distributed caching with Redis
Cache
Interface representing a single cache.
public interface Cache {
// Get cache name
String getName();
// Get native cache implementation
Object getNativeCache();
// Get value by key
ValueWrapper get(Object key);
// Put value in cache
void put(Object key, Object value);
// Remove entry from cache
void evict(Object key);
// Clear entire cache
void clear();
}
Cache Annotations
| Annotation | Purpose | Target | Parameters |
|---|---|---|---|
@Cacheable |
Cache method result before execution | Methods | value, key, condition, unless |
@CachePut |
Always execute, then cache result | Methods | value, key, condition, unless |
@CacheEvict |
Remove entry/entries from cache | Methods | value, key, allEntries, condition, beforeInvocation |
@Caching |
Combine multiple cache operations | Methods | cacheable, put, evict |
@CacheConfig |
Class-level cache configuration | Classes | cacheNames |
@EnableCaching |
Enable caching support | Configuration classes | None |
Annotation Parameters
value / cacheNames
Name(s) of the cache(s) to use.
@Cacheable(value = "products") // Single cache
@Cacheable(value = {"products", "inventory"}) // Multiple caches
key
SpEL expression to generate cache key (if not using method parameters as key).
@Cacheable(value = "products", key = "#id")
@Cacheable(value = "products", key = "#p0") // First parameter
@Cacheable(value = "products", key = "#root.methodName + #id")
@Cacheable(value = "products", key = "T(java.util.Objects).hash(#id, #name)")
SpEL Context Variables:
#root.methodName- Method name being invoked#root.method- Method object#root.target- Target object#root.targetClass- Target class#root.args[0]- Method arguments array#a0,#p0- First argument#result- Method result (only in @CachePut, @CacheEvict)
condition
SpEL expression evaluated before cache operation. Operation only executes if true.
@Cacheable(value = "products", condition = "#id > 0")
@Cacheable(value = "products", condition = "#price > 100 && #active == true")
@Cacheable(value = "products", condition = "#size() > 0") // For collections
unless
SpEL expression evaluated AFTER method execution. Entry is cached only if false.
@Cacheable(value = "products", unless = "#result == null")
@CachePut(value = "products", unless = "#result.isPrivate()")
beforeInvocation
For @CacheEvict only. If true, cache is evicted BEFORE method execution (default: false).
@CacheEvict(value = "products", beforeInvocation = true) // Evict before call
@CacheEvict(value = "products", beforeInvocation = false) // Evict after call
allEntries
For @CacheEvict only. If true, entire cache is cleared instead of single entry.
@CacheEvict(value = "products", allEntries = true) // Clear all entries
Configuration Reference
Maven Dependencies
<!-- Spring Cache Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>3.5.6</version>
</dependency>
<!-- Caffeine (Optional, for advanced caching) -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.6</version>
</dependency>
<!-- EhCache (Optional, for distributed caching) -->
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version>
<classifier>jakarta</classifier>
</dependency>
<!-- Redis (Optional, for distributed caching) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.5.6</version>
</dependency>
Gradle Dependencies
dependencies {
// Spring Cache Starter
implementation 'org.springframework.boot:spring-boot-starter-cache:3.5.6'
// Caffeine
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.6'
// EhCache
implementation 'javax.cache:cache-api:1.1.1'
implementation 'org.ehcache:ehcache:3.10.8'
// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis:3.5.6'
}
Application Properties (application.properties)
# General Caching Configuration
spring.cache.type=simple # Type: simple, redis, caffeine, ehcache, jcache
# Caffeine Configuration
spring.cache.caffeine.spec=maximumSize=1000,expireAfterWrite=10m
spring.cache.cache-names=products,users,orders
# Redis Configuration
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.password=
spring.cache.redis.time-to-live=600000 # 10 minutes in ms
# EhCache Configuration
spring.cache.jcache.config=classpath:ehcache.xml
Application Properties (application.yml)
spring:
cache:
type: simple
cache-names:
- products
- users
- orders
caffeine:
spec: maximumSize=1000,expireAfterWrite=10m
redis:
time-to-live: 600000 # 10 minutes in ms
jcache:
config: classpath:ehcache.xml
Performance Tuning Reference
Cache Types Comparison
| Type | Use Case | Memory | Thread-Safe | Distributed |
|---|---|---|---|---|
| Simple | Local, small data | Low | Yes | No |
| Caffeine | High-performance local | Medium | Yes | No |
| EhCache | Enterprise local | High | Yes | Optional |
| Redis | Distributed, large | External | Yes | Yes |
Performance Tips
1. Key Generation Strategy:
// Fast (uses method parameters directly)
@Cacheable(value = "products") // Uses all parameters as key
@Cacheable(value = "products", key = "#id") // Specific parameter
// Slower (computed SpEL)
@Cacheable(value = "products", key = "T(java.util.Objects).hash(#id, #name)")
2. Cache Size Tuning:
# Caffeine: Set appropriate maximumSize
spring.cache.caffeine.spec=maximumSize=10000,expireAfterWrite=15m
# Redis: Monitor memory usage
# MEMORY STATS command in Redis CLI
3. TTL Configuration:
# Redis: TTL in milliseconds
spring.cache.redis.time-to-live=600000 # 10 minutes
# Caffeine: In spec
spring.cache.caffeine.spec=expireAfterWrite=10m
Spring Boot Auto-Configuration
Auto-Detected Cache Managers
Spring Boot auto-configures a CacheManager based on classpath presence (in priority order):
- Redis - if
spring-boot-starter-data-redisis present - Caffeine - if
caffeinelibrary is present - EhCache - if
ehcachelibrary is present - Simple - default in-memory caching
To explicitly set the cache type:
spring.cache.type=redis
Conditional Bean Creation
@Bean
@ConditionalOnMissingBean(CacheManager.class)
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("products", "users");
}
Transaction Integration
Cache + @Transactional Interaction
@Service
@Transactional
public class ProductService {
@Cacheable(value = "products", key = "#id")
@Transactional(readOnly = true) // Combines with cache
public Product getProduct(Long id) {
return productRepository.findById(id).orElse(null);
}
@CachePut(value = "products", key = "#product.id")
@Transactional // Ensure atomicity of save + cache update
public Product updateProduct(Product product) {
return productRepository.save(product);
}
@CacheEvict(value = "products", key = "#id")
@Transactional
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
}
Monitoring and Metrics
Spring Boot Actuator Integration
# Enable caching metrics
management.endpoints.web.exposure.include=metrics,health
# View cache metrics
GET http://localhost:8080/actuator/metrics
GET http://localhost:8080/actuator/metrics/cache.hits
GET http://localhost:8080/actuator/metrics/cache.misses
Custom Cache Metrics
@Component
public class CacheMetricsCollector {
private final MeterRegistry meterRegistry;
public void recordCacheHit(String cacheName) {
meterRegistry.counter("cache.hits", "cache", cacheName).increment();
}
public void recordCacheMiss(String cacheName) {
meterRegistry.counter("cache.misses", "cache", cacheName).increment();
}
}
EhCache XML Configuration Reference
ehcache.xml Structure
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ehcache.org/v3"
xmlns:jsr107="http://www.ehcache.org/v3/jsr107"
xsi:schemaLocation="
http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">
<!-- Cache Configuration -->
<cache alias="cacheName">
<key-type>java.lang.Long</key-type>
<value-type>com.example.Product</value-type>
<!-- Time to Live -->
<expiry>
<ttl unit="minutes">30</ttl>
</expiry>
<!-- Storage Configuration -->
<resources>
<heap unit="entries">1000</heap>
<offheap unit="MB">50</offheap>
<disk unit="GB">1</disk>
</resources>
<!-- Listeners (optional) -->
<listeners>
<listener>
<class>com.example.CustomCacheEventListener</class>
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
</listener>
</listeners>
</cache>
</config>
Common EhCache Attributes
heap- On-heap memory storage (fast, limited)offheap- Off-heap memory storage (slower, larger)disk- Disk storage (slowest, unlimited)ttl- Time to live before expirationidle- Time to idle before expiration (if not accessed)
Common Pitfalls and Solutions
Problem 1: Cache Not Working
Symptoms: Cache is never hit, always querying database.
Causes & Solutions:
// Problem: @Cacheable on public method called from same bean
@Service
public class ProductService {
@Cacheable("products")
public Product get(Long id) { }
public Product getDetails(Long id) {
return this.get(id); // ❌ Won't use cache (no proxy)
}
}
// Solution: Inject service or call through interface
@Service
public class DetailsService {
@Autowired
private ProductService productService;
public Product getDetails(Long id) {
return productService.get(id); // ✅ Uses cache
}
}
// Problem: Caching non-serializable objects with Redis
@Cacheable("products")
public Product get(Long id) {
Product p = new Product();
p.setConnection(dbConnection); // ❌ Not serializable
return p;
}
// Solution: Ensure all cached objects are serializable
@Cacheable("products")
public ProductDTO get(Long id) {
return mapper.toDTO(productRepository.findById(id)); // ✅ DTO is serializable
}
Problem 2: Stale Cache Data
Symptoms: Updates aren't reflected in cached data.
Solution:
// Always evict cache on update
@CacheEvict(value = "products", key = "#id")
public void updateProduct(Long id, UpdateRequest req) {
Product product = productRepository.findById(id).orElseThrow();
product.update(req);
productRepository.save(product);
}
// Or use @CachePut to keep cache fresh
@CachePut(value = "products", key = "#result.id")
public Product updateProduct(Long id, UpdateRequest req) {
Product product = productRepository.findById(id).orElseThrow();
product.update(req);
return productRepository.save(product);
}
Problem 3: Memory Leak
Symptoms: Memory usage grows unbounded.
Solution:
# Configure cache eviction policies
spring.cache.caffeine.spec=maximumSize=10000,expireAfterWrite=10m
# Redis: Set TTL
spring.cache.redis.time-to-live=600000
# Monitor cache size
External Resources
Official Documentation
Third-Party Libraries
Related Skills
- spring-boot-performance-tuning - Comprehensive performance optimization
- spring-boot-data-persistence - Database optimization patterns
- spring-boot-rest-api-standards - API design with caching headers
Useful Articles
- Spring Cache Abstraction Tutorial
- Redis Caching in Spring Boot
- Cache Stampede Problem
- Cache Invalidation Strategies
SpEL Reference for Cache Keys
Basic Expressions
// Method parameters
@Cacheable(key = "#id") // Single parameter
@Cacheable(key = "#user.id") // Object property
@Cacheable(key = "#root.args[0]") // First argument
// Composite keys
@Cacheable(key = "#id + '-' + #type")
@Cacheable(key = "T(java.util.Objects).hash(#id, #type)")
// Collections
@Cacheable(key = "#ids.toString()")
@Cacheable(condition = "#ids.size() > 0")
SpEL Context Variables
| Variable | Description |
|---|---|
#root.method |
Method object |
#root.methodName |
Method name |
#root.target |
Target object |
#root.targetClass |
Target class |
#root.args |
Arguments array |
#p<index> |
Argument at index |
#<name> |
Named argument |
#result |
Method result (@CachePut, @CacheEvict) |
Testing Reference
Testing Cache Behavior
@Test
void shouldCacheResult() {
// Arrange
when(repository.find(1L)).thenReturn(mockObject);
// Act - First call
service.get(1L);
// Assert - Database was queried
verify(repository, times(1)).find(1L);
// Act - Second call
service.get(1L);
// Assert - Database NOT queried again (cache hit)
verify(repository, times(1)).find(1L);
}
Disabling Cache in Tests
@SpringBootTest
@PropertySource("classpath:application-test.properties")
class MyServiceTest {
// In application-test.properties:
// spring.cache.type=none
}