Caching is a performance optimization technique that stores frequently accessed data in memory to avoid repetitive and expensive computations or database calls.
1. Why Use Cache?
- Reduce database access latency
- Improve throughput for read-heavy applications
- Lower load on the backend database
2. Types of Caches
1. First-Level Cache (Persistence Context Cache)
- Scope: Per EntityManager
- Enabled by default in JPA
- Behavior: Caches entities within a transaction or session
- Usage: Transparent to developers
2. Second-Level Cache
- Scope: Shared across sessions/transactions
- Enabled manually with caching providers like:
- EhCache
- Caffeine
- Infinispan
- Usage: Configure with JPA and provider-specific XML or Java config
- Annotations: Works with
@Cacheable,@CacheEvict
3. Application-Level Cache (Spring Cache Abstraction)
Spring Cache abstracts the caching mechanism and lets you plug in your own provider:
- Simple Cache (Default): Uses
ConcurrentMapCacheManager– basic, thread-safe in-memory cache - EhCache: XML-configurable, mature, widely used
- Caffeine: High-performance Java caching library
- Redis (external): Suitable for distributed systems and large-scale caching
2.1 Steps to Add Each Cache Provider
✅ Overview of All Cache Managers in Spring Boot
Spring Boot auto-configures these commonly used cache managers:
| Cache Manager | Description |
|---|---|
ConcurrentMapCacheManager | Default, simple in-memory, thread-safe |
EhCacheCacheManager | Wraps EhCache (requires ehcache.xml or config) |
JCacheCacheManager | JSR-107 standard, used with EhCache 3, Hazelcast |
CaffeineCacheManager | High-performance local cache |
RedisCacheManager | Redis-based distributed cache |
SimpleCacheManager | Programmatic setup with custom Cache beans |
NoOpCacheManager | Disables caching, useful for testing |
✅ How to Choose Cache Manager
Spring Boot detects the cache manager based on the available dependencies:
- No dependency:
ConcurrentMapCacheManageris used - Ehcache:
EhCacheCacheManager - Caffeine:
CaffeineCacheManager - JCache (JSR-107):
JCacheCacheManager - Redis:
RedisCacheManager
You can override the default by providing a @Bean of type CacheManager in a @Configuration class.
✅ Example to Select a Specific Cache Manager
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new CaffeineCacheManager("users", "products");
}
}
Each manager can have its own behavior regarding TTL, eviction policy, serialization, etc., and should be chosen based on the application scale and requirements.
✅ Default (Simple In-Memory Cache)
- No dependency required
- Configuration:
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("users");
}
✅ EhCache (XML-based)
Dependency:
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
Steps:
- Create
ehcache.xmlunderresources - Define cache name, max entries, TTL, etc.
- Configure EhCacheManager:
@Bean
public JCacheManagerCustomizer customizer() {
return cm -> cm.createCache("users", new MutableConfiguration<>());
}
✅ Caffeine (Recommended for high performance)
Dependency:
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
Configuration:
@Bean
public CacheManager caffeineCacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager("users");
manager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(100));
return manager;
}
✅ Redis (for distributed caching)
Dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Configuration:
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
When to Use What:
| Cache Type | Best For |
|---|---|
| Simple | Small apps, testing, no external deps |
| EhCache | XML-configurable legacy enterprise apps |
| Caffeine | High-performance JVM-based apps |
| Redis | Distributed apps or multi-node architecture |
1. First-Level Cache (Persistence Context Cache)
- Scope: Per EntityManager
- Enabled by default in JPA
- Behavior: Caches entities within a transaction or session
- Usage: Transparent to developers
2. Second-Level Cache
- Scope: Shared across sessions/transactions
- Configured manually using a provider like:
- EhCache
- Caffeine
- Infinispan
- Needs annotations:
@Cacheable,@CacheEvict
3. Application-Level Cache (Spring Cache Abstraction)
- Provided by Spring Framework
- Use any caching provider (EhCache, Redis, etc.)
3. Spring Cache Annotations
@Cacheable
Caches method results.
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {...}
Attributes:
value: cache namekey: SpEL expression for custom keycondition: cache only if trueunless: skip caching if true
@CachePut
Always executes the method and updates the cache with the result.
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {...}
@CacheEvict
Removes data from the cache.
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {...}
Attributes:
key: evict specific keyallEntries = true: remove all entriesbeforeInvocation = true: evict before method runs
4. Example with Configuration
Setting Expiry for Each Cache
Expiration (TTL – Time To Live) defines how long an entry stays in the cache. It is supported differently by cache providers.
✅ Caffeine (per cache or global)
@Bean
public CacheManager caffeineCacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager("users", "products");
manager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(100));
return manager;
}
✅ Redis
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration usersCache = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5));
RedisCacheConfiguration productsCache = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1));
Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();
cacheConfigs.put("users", usersCache);
cacheConfigs.put("products", productsCache);
return RedisCacheManager.builder(connectionFactory)
.withInitialCacheConfigurations(cacheConfigs)
.build();
}
✅ EhCache
Configure TTL in ehcache.xml:
<cache name="users" maxEntriesLocalHeap="1000" timeToLiveSeconds="600" /> <cache name="products" maxEntriesLocalHeap="1000" timeToLiveSeconds="3600" />
✅ Simple Cache (ConcurrentMapCacheManager)
Does not support TTL by default. Use alternative like Caffeine or Redis for expiry-based caching.
Example 1: Simple Cache with Custom Key and Condition
@Cacheable(value = "products", key = "#productId", condition = "#productId > 10", unless = "#result == null")
public Product getProductById(Long productId) {
return productRepository.findById(productId).orElse(null);
}
Example 2: Cache with All Entries and Custom Parameter Logic
@CacheEvict(value = "products", allEntries = true, beforeInvocation = false)
public void clearProductCache() {
// This method will clear all entries from 'products' cache
}
Example 3: Cache Based on Complex Object Parameter
@Cacheable(value = "users", key = "#user.email + ':' + #user.country")
public User getUserDetails(User user) {
return userService.findByEmailAndCountry(user.getEmail(), user.getCountry());
}
Example 1: Simple Cache with Custom Key and Condition
@Cacheable(value = "products", key = "#productId", condition = "#productId > 10", unless = "#result == null")
public Product getProductById(Long productId) {
return productRepository.findById(productId).orElse(null);
}
value: Defines the cache namekey: Uses the method parameterproductIdas the cache keycondition: Caches only ifproductIdis greater than 10unless: Skips caching if the result is null
Example 2: Cache with All Entries and Custom Parameter Logic
@CacheEvict(value = "products", allEntries = true, beforeInvocation = false)
public void clearProductCache() {
// This method will clear all entries from 'products' cache
}
allEntries = true: Clears the entire cache regionbeforeInvocation = false: Eviction happens after method execution
Example 3: Cache Based on Complex Object Parameter
@Cacheable(value = "users", key = "#user.email + ':' + #user.country")
public User getUserDetails(User user) {
return userService.findByEmailAndCountry(user.getEmail(), user.getCountry());
}
key: Composite key generated from multiple fields of the input object
@EnableCaching
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("users", "products");
}
}
5. Lazy vs Eager Loading
Lazy Loading
- Loads associations on-demand
- Default strategy
- Requires open session when accessing
@ManyToOne(fetch = FetchType.LAZY) private Department department;
Eager Loading
- Loads associations immediately
- Can lead to unnecessary joins
@ManyToOne(fetch = FetchType.EAGER) private Department department;
When to Use:
- Lazy: Most cases; better performance
- Eager: When you always need associated data immediately
6. JPA Proxies
JPA uses proxy classes to support lazy loading. These are subclasses generated at runtime.
Example:
Employee emp = entityManager.find(Employee.class, 1L); Department dept = emp.getDepartment(); // department is a proxy
Note: Accessing a proxy outside of the transaction/session will throw LazyInitializationException.
7. Best Practices for Caching
- Use
@Cacheableon id-based read methods - Evict or update cache on writes with
@CacheEvictor@CachePut - Use meaningful
keyvalues to avoid collisions - Don’t cache frequently changing data
- Always monitor memory usage and cache hit rates
- Use TTL (time to live) settings in external caches
8. Visual Diagram of Cache Flow
+-------------------+
| Spring Service |
+---------+---------+
|
v
+--------+--------+
| Spring Cache | <-- First-level (if inside JPA Tx)
+--------+--------+
|
v
+--------+--------+
| EntityManager |
+--------+--------+
|
v
Database