Clean • Professional
Caching is one of the most effective ways to improve performance in modern Spring Boot applications. Instead of writing manual cache logic, the Spring Framework provides a powerful cache abstraction that lets you control caching behavior using simple annotations.
Spring Cache is a caching abstraction provided by the Spring Framework that helps you add caching to your application without mixing caching logic into your business code.
It allows you to:
Spring itself does not store cache data. It only provides the abstraction layer.
It integrates with providers like:
You only configure the provider. Your annotations remain the same.
Spring caching is built on AOP (Aspect-Oriented Programming).
Spring creates a proxy around your bean. That proxy intercepts method calls and decides whether to:
Execution Flow

Important concepts:
Self-invocation does not trigger caching because proxy is bypassed.
If you're using Spring Boot, enabling caching is very simple.
Step 1: Add Dependency
For Redis (Distributed Cache)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
For Caffeine (Local Cache)
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
Step 2: Enable Caching
@Configuration
@EnableCaching
public class CacheConfig {
}
That’s it. Spring caching is now active.
When working with caching in Spring Boot, you mainly use four powerful annotations provided by the Spring Framework caching abstraction.
Used mainly for read operations.
Behavior
Example
@Cacheable(value = "products", key = "#id")
public Product getProductById(Long id) {
System.out.println("Fetching from DB...");
return productRepository.findById(id).orElse(null);
}
What Happens?
First call:
Fetching from DB...
Second call:
No log printed. Returned directly from cache.
| Attribute | Meaning |
|---|---|
| value | Cache name |
| key | Custom cache key (SpEL expression) |
| condition | Cache only if condition is true |
| unless | Skip caching if condition is true |
| sync | Prevent cache stampede |
Conditional Caching
Cache only when ID > 10:
@Cacheable(value = "products", condition = "#id > 10")
Avoid caching null results:
@Cacheable(value = "products", unless = "#result == null")
Used for update operations.
Example
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
return productRepository.save(product);
}
Keeps cache synchronized after updates.
Used when:
Remove Specific Entry
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
Clear Entire Cache
@CacheEvict(value = "products", allEntries = true)
public void clearCache() {
}
Evict Before Execution
@CacheEvict(value = "products", key = "#id", beforeInvocation = true)
Setting beforeInvocation = true ensures cache removal even if the method fails.
Used when combining multiple cache behaviors in one method.
@Caching(
put = @CachePut(value = "products", key = "#product.id"),
evict = @CacheEvict(value = "productList", allEntries = true)
)
public Product updateProduct(Product product) {
return productRepository.save(product);
}
When working with caching in Spring Boot, cache key design is one of the most important decisions you’ll make. A bad key strategy can silently break your system.
Poor key design leads to:
If you don’t define a key manually, Spring generates one automatically.
SimpleKey objectSimpleKey.EMPTYExample
@Cacheable("users")
public User getUser(String username, String role) {
return userRepository.find(username, role);
}
Internally, Spring creates:
SimpleKey [username, role]
This works — but it’s not human-readable in Redis and harder to debug.
Using SpEL (Spring Expression Language) gives you full control.
Example
@Cacheable(value = "users", key = "#username + '_' + #role")
Generated key:
users::john_ADMIN
@Cacheable(value = "users", key = "'user:' + #username + ':' + #role")
Generated key:
users::user:john:ADMIN
If you want organization-wide key standardization:
@Bean
public KeyGenerator customKeyGenerator() {
return (target, method, params) ->
method.getName() + "_" + Arrays.toString(params);
}
Then use it:
@Cacheable(value = "users", keyGenerator = "customKeyGenerator")
When Should You Use This?
Cache stampede happens when:
Very common in high-traffic e-commerce systems.
Spring Solution: sync = true
@Cacheable(value = "products", key = "#id", sync = true)
public Product getProduct(Long id) {
return repository.findById(id).orElse(null);
}
This ensures only one thread loads data.
Spring annotations do not control expiration. TTL is configured in the cache provider.
Example: Redis TTL
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config =
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
Best Practice for TTL
E-commerce Product Service
@Service
public class ProductService {
@Cacheable(value = "products", key = "'product:' + #id", sync = true)
public Product getById(Long id) {
return repository.findById(id).orElse(null);
}
@CachePut(value = "products", key = "'product:' + #product.id")
public Product update(Product product) {
return repository.save(product);
}
@CacheEvict(value = "products", key = "'product:' + #id")
public void delete(Long id) {
repository.deleteById(id);
}
}
Flow
When using Spring Boot, avoid these mistakes:
@EnableCaching – caching won’t work@Cacheable for read-heavy methods@CachePut for updates@CacheEvict on delete| Feature | Caffeine (Local Cache) | Redis (Distributed Cache) |
|---|---|---|
| Location | Inside JVM memory | External server |
| Shared Across Services | No | Yes |
| Speed | Ultra-fast (no network call) | Very fast (network-based) |
| Best For | Single service optimization | Scalable microservices |
Spring Cache Annotations provide a clean and powerful way to implement caching in Spring Boot applications.
Using @Cacheable, @CachePut, and @CacheEvict, you can improve performance, reduce database load, and keep data synchronized with minimal code.
When paired with providers like Caffeine (local) or Redis (distributed), Spring caching becomes production-ready and scalable.