Caching is a key strategy in software development to enhance application performance and optimize resource utilization. In the context of Java applications, Spring Boot provides an elegant and straightforward way to implement caching using its built-in framework support. This article explores the essentials of Spring Boot caching, from its core concepts to practical implementations, helping developers leverage its power to build high-performance applications.
What is Caching?
Caching stores frequently accessed data in memory so that subsequent requests for the same data can be served faster. Caching can significantly improve application speed and responsiveness by reducing the time spent on expensive operations like database queries, computations, or API calls.
Key benefits of caching include:
- Improved Performance: Minimizes latency by retrieving data from a faster storage layer.
- Reduced Load: Decreases the load on external resources like databases or APIs.
- Enhanced Scalability: Handles a higher volume of requests efficiently.
Spring Boot Caching Overview
Spring Boot builds on the Spring Framework’s abstraction for caching, offering a declarative programming model. It allows developers to integrate caching effortlessly using annotations. Spring Boot supports multiple caching providers like EhCache, Redis, Hazelcast, and Caffeine, making it flexible for diverse use cases.
Core Components of Spring Boot Caching
- Cache Abstraction: Spring’s caching abstraction provides a consistent programming model across different implementations.
- Annotations:
@EnableCaching
: Activates Spring’s annotation-driven cache management.@Cacheable
: Indicates that a method’s result should be cached.@CachePut
: Updates the cache without affecting the method execution.@CacheEvict
: Removes entries from the cache.@Caching
: Allows combining multiple cache operations.
- Cache Manager: The
CacheManager
interface defines the contract for cache management and works with underlying cache providers.
Setting Up Spring Boot Caching
To get started with caching in Spring Boot, follow these steps:
Step 1: Add Dependencies
Include the necessary dependencies in your pom.xml
or build.gradle
file based on your caching provider. For example, for EhCache:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
Step 2: Enable Caching
Enable caching in your Spring Boot application by adding the @EnableCaching
annotation to a configuration class:
@Configuration @EnableCaching public class CacheConfig { // Additional cache configuration (optional) }
Step 3: Configure Cache Manager
Spring Boot provides default configurations for popular cache providers. For custom setups, define a bean for CacheManager
:
@Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager("users", "products"); }
Using Spring Boot Caching Annotations
1. @Cacheable
The @Cacheable
annotation caches the result of a method based on its parameters. If the same parameters are passed again, the cached result is returned.
@Cacheable("users") public User getUserById(Long id) { // Expensive database operation return userRepository.findById(id).orElseThrow(() -> new UserNotFoundException()); }
2. @CachePut
The @CachePut
annotation updates the cache with the method’s result every time it is invoked.
@CachePut(value = "users", key = "#user.id") public User updateUser(User user) { return userRepository.save(user); }
3. @CacheEvict
The @CacheEvict
annotation removes entries from the cache for a specific key or all entries.
@CacheEvict(value = "users", key = "#id") public void deleteUser(Long id) { userRepository.deleteById(id); }
To clear all entries in a cache, use:
@CacheEvict(value = "users", allEntries = true) public void clearUsersCache() { // Clear operation }
4. @Caching
For complex scenarios, use @Caching
to combine multiple caching operations:
@Caching( put = { @CachePut(value = "users", key = "#user.id") }, evict = { @CacheEvict(value = "users", key = "#user.id") } ) public User saveOrUpdateUser(User user) { return userRepository.save(user); }
5. Create RESTful
Create a rest controller.
@RestController @RequestMapping("/api/v1/users-cache") @Slf4j @RequiredArgsConstructor public class UserCacheController { private final UserProfileCacheService userProfileCacheService; private final CacheManager cacheManager; @GetMapping("/{id}") public ResponseEntity<UserProfile> getUserById(@PathVariable String id) { UUID uuid = UUID.fromString(id); UserProfile user = userProfileCacheService.getUserById(uuid); printAllCachesAndEntries(); return ResponseEntity.ok(user); } @PostMapping("/{name}") public ResponseEntity<UserProfile> createUserProfile(@PathVariable String name) { // Create a demo UserProfile instance using @Builder UserProfile demoUserProfile = new UserProfile(); demoUserProfile.setId(UUID.randomUUID()); demoUserProfile.setFirstName(name); demoUserProfile.setLastName("Doe"); demoUserProfile.setEmail("john.doe@example.com"); demoUserProfile.setBirthDate(LocalDateTime.of(1990, 5, 15, 0, 0)); demoUserProfile.setSex(1); demoUserProfile.setCreateBy(UUID.randomUUID()); demoUserProfile.setCreateDate(LocalDateTime.now()); demoUserProfile.setUpdateBy(null); demoUserProfile.setUpdateDate(null); UserProfile user = userProfileCacheService.updateUser(demoUserProfile); printAllCachesAndEntries(); return ResponseEntity.ok(user); } @DeleteMapping("/{id}") public ResponseEntity<String> deleteUserProfile(@PathVariable String id) { UUID uuid = UUID.fromString(id); userProfileCacheService.deleteUser(uuid); UUID key = findCacheKeyByUserProfileId(uuid); userProfileCacheService.deleteCacheByKey(key); printAllCachesAndEntries(); return ResponseEntity.ok("delete success."); } @DeleteMapping public ResponseEntity<String> clearCache() { userProfileCacheService.clearUsersCache(); printAllCachesAndEntries(); return ResponseEntity.ok("clear cache success."); } public void printAllCachesAndEntries() { if (cacheManager instanceof ConcurrentMapCacheManager concurrentMapCacheManager) { for (String cacheName : concurrentMapCacheManager.getCacheNames()) { log.info("Cache Name: {}", cacheName); Cache cache = concurrentMapCacheManager.getCache(cacheName); if (cache instanceof ConcurrentMapCache concurrentMapCache) { Map<Object, Object> nativeCache = concurrentMapCache.getNativeCache(); if (nativeCache.isEmpty()) { log.info("Cache is empty."); } else { nativeCache.forEach((key, value) -> log.info("Key: {}, Value: {}", key , value) ); } } else { log.info("Cache is not an instance of ConcurrentMapCache."); } log.info("--------------------------------------------------"); } } else { log.info("CacheManager is not an instance of ConcurrentMapCacheManager."); } } public UUID findCacheKeyByUserProfileId(UUID userProfileId) { Cache cache = cacheManager.getCache("users"); if (cache != null) { if (cache instanceof ConcurrentMapCache concurrentMapCache) { Map<Object, Object> nativeCache = concurrentMapCache.getNativeCache(); for (Map.Entry<Object, Object> entry : nativeCache.entrySet()) { UserProfile userProfile = (UserProfile) entry.getValue(); if (userProfile.getId().equals(userProfileId)) { return (UUID)entry.getKey(); // Return the cache key (UUID) } } } } return null; // Return null if no match found } }
Create a cache service.
@Service @RequiredArgsConstructor public class UserProfileCacheService { private final UserProfileRepository userProfileRepository; @Cacheable("users") public UserProfile getUserById(UUID uuid) { return userProfileRepository.findById(uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); } @CachePut(value = "users", key = "#user.id") public UserProfile updateUser(UserProfile user) { return userProfileRepository.save(user); } public void deleteUser(UUID id) { userProfileRepository.deleteById(id); } @CacheEvict(value = "users", key = "#id") public void deleteCacheByKey(UUID id) { // } @CacheEvict(value = "users", allEntries = true) public void clearUsersCache() { } }
Create a user profile bean.
@Data @Entity @Table(name = "user_profiles", schema = "public") @NoArgsConstructor public class UserProfile { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; @Column(name = "first_name", nullable = false) private String firstName; @Column(name = "last_name", nullable = false) private String lastName; @Column(name = "email", nullable = false, unique = true) private String email; @Column(name = "birth_date", nullable = false) private LocalDateTime birthDate; @Column(name = "sex", nullable = false) private Integer sex; @Column(name = "create_by", nullable = false) private UUID createBy; @Column(name = "create_date", nullable = false) private LocalDateTime createDate; @Column(name = "update_by") private UUID updateBy; @Column(name = "update_date") private LocalDateTime updateDate; }
Create a User profile repository.
@Repository public interface UserProfileRepository extends JpaRepository<UserProfile, UUID> { }
Create a cache config.
@Configuration @EnableCaching public class CacheConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager("users"); } }
6. Create a database
The developer creates a database following this tutorial.



Test case scenario

1. GET User by ID:
/api/v1/users-cache/81f867c4–86e9–47e6–9f50-bbf22dfe13eb
{ "id": "81f867c4-86e9-47e6-9f50-bbf22dfe13eb", "firstName": "Cathleen", "lastName": "Mccall", "email": "cathleen.m@example.com", "birthDate": "1985-06-06T00:00:00", "sex": 2, "createBy": "4d4f65ee-cfe3-4431-88e4-b4142eccb739", "createDate": "2024-06-04T16:26:39.325342", "updateBy": null, "updateDate": null }
First request.
Hibernate: select up1_0.id,up1_0.birth_date,up1_0.create_by,up1_0.create_date,up1_0.email,up1_0.first_name,up1_0.last_name,up1_0.sex,up1_0.update_by,up1_0.update_date from public.user_profiles up1_0 where up1_0.id=? c.e.p.controller.UserCacheController : Cache Name: users c.e.p.controller.UserCacheController : Key: 81f867c4-86e9-47e6-9f50-bbf22dfe13eb, Value: UserProfile(id=81f867c4-86e9-47e6-9f50-bbf22dfe13eb, firstName=Cathleen, lastName=Mccall, email=cathleen.m@example.com, birthDate=1985-06-06T00:00, sex=2, createBy=4d4f65ee-cfe3-4431-88e4-b4142eccb739, createDate=2024-06-04T16:26:39.325342, updateBy=null, updateDate=null) c.e.p.controller.UserCacheController : --------------------------------------------------
Second request.
c.e.p.controller.UserCacheController : Cache Name: users c.e.p.controller.UserCacheController : Key: 81f867c4-86e9-47e6-9f50-bbf22dfe13eb, Value: UserProfile(id=81f867c4-86e9-47e6-9f50-bbf22dfe13eb, firstName=Cathleen, lastName=Mccall, email=cathleen.m@example.com, birthDate=1985-06-06T00:00, sex=2, createBy=4d4f65ee-cfe3-4431-88e4-b4142eccb739, createDate=2024-06-04T16:26:39.325342, updateBy=null, updateDate=null) c.e.p.controller.UserCacheController : --------------------------------------------------
On the second request, there is no database query. The response sends data from the cache.
2. POST Create a user by ID:/api/v1/users-cache/smith

Hibernate: insert into public.user_profiles (birth_date,create_by,create_date,email,first_name,last_name,sex,update_by,update_date,id) values (?,?,?,?,?,?,?,?,?,?) c.e.p.controller.UserCacheController : Cache Name: users c.e.p.controller.UserCacheController : Key: 6d43e9be-4e74-4828-a833-14db5ed991c3, Value: UserProfile(id=ba82bcf2-605f-4c82-8bde-8f75a04c4d1a, firstName=smith, lastName=Doe, email=john.doe@example.com, birthDate=1990-05-15T00:00, sex=1, createBy=a8ad9387-1b08-4de5-8b58-b3ddd821b531, createDate=2024-12-19T16:26:44.746871600, updateBy=null, updateDate=null) c.e.p.controller.UserCacheController : Key: 81f867c4-86e9-47e6-9f50-bbf22dfe13eb, Value: UserProfile(id=81f867c4-86e9-47e6-9f50-bbf22dfe13eb, firstName=Cathleen, lastName=Mccall, email=cathleen.m@example.com, birthDate=1985-06-06T00:00, sex=2, createBy=4d4f65ee-cfe3-4431-88e4-b4142eccb739, createDate=2024-06-04T16:26:39.325342, updateBy=null, updateDate=null) c.e.p.controller.UserCacheController : --------------------------------------------------
The user name Smith has been created and added to the cache.

3. DELETE User by ID:
/api/v1/users-cache/ba82bcf2-605f-4c82-8bde-8f75a04c4d1a

Hibernate: delete from public.user_profiles where id=? c.e.p.controller.UserCacheController : Cache Name: users c.e.p.controller.UserCacheController : Key: 81f867c4-86e9-47e6-9f50-bbf22dfe13eb, Value: UserProfile(id=81f867c4-86e9-47e6-9f50-bbf22dfe13eb, firstName=Cathleen, lastName=Mccall, email=cathleen.m@example.com, birthDate=1985-06-06T00:00, sex=2, createBy=4d4f65ee-cfe3-4431-88e4-b4142eccb739, createDate=2024-06-04T16:26:39.325342, updateBy=null, updateDate=null) c.e.p.controller.UserCacheController : --------------------------------------------------
The user has been deleted.

4. Clear all cache: /api/v1/users-cache/
Data in cache
c.e.p.controller.UserCacheController : Cache Name: users c.e.p.controller.UserCacheController : Key: a57dcfd0-9703-4852-ac89-1fb3d54310a9, Value: UserProfile(id=a57dcfd0-9703-4852-ac89-1fb3d54310a9, firstName=Elly, lastName=Doe, email=john.doe@example.com, birthDate=1990-05-15T00:00, sex=1, createBy=8b670765-5db2-4c78-8d90-f69bde7f799f, createDate=2024-12-19T17:06:41.172328, updateBy=null, updateDate=null) c.e.p.controller.UserCacheController : --------------------------------------------------
Send a request to clear the cache.

c.e.p.controller.UserCacheController : Cache Name: users c.e.p.controller.UserCacheController : Cache is empty. c.e.p.controller.UserCacheController : --------------------------------------------------
The cache is empty when sending a request to clear the cache.
Conclusion
Spring Boot caching is a powerful feature that enables developers to optimize application performance with minimal effort. By leveraging annotations like @Cacheable
, @CachePut
, and @CacheEvict
, developers can seamlessly integrate caching into their applications. Whether you use in-memory solutions like EhCache or distributed systems like Redis, Spring Boot provides the flexibility to meet diverse caching needs.
Integrating caching strategically ensures efficient resource usage, faster response times, and better scalability — key factors for building modern, high-performing applications.