Mastering SpringBoot Cache Annotations: @Cacheable and @CacheEvict
This article explains why and how to use SpringBoot's built‑in cache annotations—@Cacheable, @CacheEvict, @CachePut and @Caching—covering environment setup, annotation attributes, practical code examples, common pitfalls, and a concise comparison to help developers simplify caching logic and keep data consistent.
Why use cache annotations
Non‑intrusive: business logic and cache logic are separated.
Minimal code: a single annotation replaces explicit get / set calls.
Centralized configuration: cache name, TTL and key rules are defined in application.yml.
Supports multiple providers such as Redis, Caffeine and in‑memory caches.
Basic environment preparation
1. Add dependency
<!-- Redis + cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>2. Enable caching in the main class
@SpringBootApplication
@EnableCaching
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}3. Configure cache in application.yml
spring:
cache:
type: redis
redis:
time-to-live: 3600000 # default 1 hour
cache-null-values: false # do not cache null valuesCore annotation @Cacheable (query + cache)
Purpose : first call executes the method, stores the result in cache; subsequent calls retrieve the result from cache without executing the method body.
1. Basic usage
@Cacheable(value = "userCache", key = "#userId")
public User getUserById(Long userId) {
return userMapper.selectById(userId);
} value / cacheNames: cache name that distinguishes business domains. key: cache key, supports SpEL expressions.
Resulting Redis key example:
userCache::10012. Common key patterns
// single parameter
@Cacheable(value = "user", key = "#id")
// object parameter, use its id
@Cacheable(value = "user", key = "#user.id")
// method name as key
@Cacheable(value = "dict", key = "#root.methodName")
// composite key
@Cacheable(value = "order", key = "'uid:' + #userId + ':type:' + #type")3. Conditional caching
// cache only adult users
@Cacheable(value = "user", key = "#userId", condition = "#result.age >= 18")
// cache only when result is not null
@Cacheable(value = "user", key = "#userId", unless = "#result == null")Core annotation @CacheEvict (delete cache)
Purpose : after data update or deletion, clear stale cache to keep data consistent.
1. Delete by key
@CacheEvict(value = "userCache", key = "#user.id")
public void updateUser(User user) {
userMapper.updateById(user);
}2. Delete all entries under a cache name
@CacheEvict(value = "userCache", allEntries = true)
public void refreshAllUser() { }3. Delete before method execution
@CacheEvict(value = "user", key = "#userId", beforeInvocation = true)Additional common annotations
1. @CachePut
Forces cache update; the method body is always executed, suitable for real‑time synchronization.
@CachePut(value = "user", key = "#user.id")
public User updateUser(User user) {
userMapper.updateById(user);
return user;
}2. @Caching
Combines multiple cache operations.
@Caching(
evict = {
@CacheEvict(value = "user", key = "#userId"),
@CacheEvict(value = "userOrder", key = "#userId")
}
)
public void deleteUser(Long userId) { }Simulated scenarios
Scenario 1 – User detail (typical query cache)
@Cacheable(value = "userInfo", key = "#userId", unless = "#result == null")
public User getUser(Long userId) {
return userMapper.selectById(userId);
}Scenario 2 – Update user → clear cache
@CacheEvict(value = "userInfo", key = "#user.id")
public void updateUser(User user) {
userMapper.updateById(user);
}Scenario 3 – Delete user → clear cache
@CacheEvict(value = "userInfo", key = "#userId")
public void deleteUser(Long userId) {
userMapper.deleteById(userId);
}Scenario 4 – Product list cache
@Cacheable(value = "productList", key = "#categoryId")
public List<Product> getProductList(Integer categoryId) {
return productMapper.selectByCategory(categoryId);
}Scenario 5 – Bulk refresh product cache
@CacheEvict(value = "productList", allEntries = true)
public void refreshProduct() { }Scenario 6 – Dictionary/config cache (rarely changes)
@Cacheable(value = "dictCache", key = "#dictType")
public List<Dict> getDict(String dictType) {
return dictMapper.selectByType(dictType);
}Precautions
Self‑invocation disables annotations : internal method calls bypass AOP proxies. Resolve by extracting the method to a separate service or injecting the bean into itself.
Cache key collisions : use distinct value/cacheNames for different business domains.
Cache‑DB inconsistency : pair write operations with @CacheEvict or @CachePut to keep cache synchronized.
Large list caching : caching huge lists can consume memory; apply shorter TTLs and consider pagination.
Null values being cached : prevent by adding unless = "#result == null" or setting cache-null-values: false in configuration.
Transaction and cache order : clear the cache after the transaction commits to avoid dirty cache.
Comparison of the three main cache annotations
@Cacheable: reads and caches; method executes only on cache miss; typical for query interfaces. @CachePut: forces cache update; method always executes; suitable for real‑time synchronization. @CacheEvict: removes cache entries; method always executes; used for create, update or delete operations.
Summary
Use @Cacheable for read‑heavy, write‑light scenarios such as user details, dictionary data or product lists.
Use @CacheEvict for updates and deletions to purge stale entries.
Use @CachePut when the cache must be refreshed on every write.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Workshop
Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
