Spring Boot
Caching in Spring Boot
Learn how Spring caching works with @EnableCaching, @Cacheable, @CacheEvict, @CachePut, cache keys, and distributed cache concerns.
The Short Version
The most common setup is: enable caching with @EnableCaching, add @Cacheable to read methods, and use @CacheEvict or @CachePut when data changes.
Step 1: Enable Caching
First, enable Spring's caching abstraction in a configuration class or on your main application class.
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
}This does not force a specific cache technology. Spring provides a caching abstraction, and the actual cache provider can be simple in-memory caching, Caffeine, Valkey, Redis, Ehcache, or another provider.
Step 2: Cache a Read Method
Use @Cacheable when a method is expensive and returns the same result for the same input.
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Cacheable(value = "products", key = "#id")
public Product getProductById(Long id) {
System.out.println("Calling database...");
return productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));
}
}The first call for a product id hits the database. Later calls with the same id can return from the cache.
What @Cacheable Means
value = "products"names the cache.key = "#id"tells Spring to use the method argument as the cache key.- If the key exists in the cache, the method body is skipped.
- If the key does not exist, the method runs and the result is stored.
Evict Cache When Data Changes
Caching reads is easy. The harder part is keeping cached data correct when writes happen.
import org.springframework.cache.annotation.CacheEvict;
@CacheEvict(value = "products", key = "#id")
public Product updateProduct(Long id, ProductRequest request) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));
product.setName(request.name());
product.setPrice(request.price());
return productRepository.save(product);
}After the product is updated, the old cached value is removed. The next read will hit the database and cache the fresh value.
Observe that the same cache (named product) is referenced in the getProductById and updateProduct methods.
Evict All Entries
Sometimes one write affects many cached entries. In that case, you can clear the whole cache.
@CacheEvict(value = "products", allEntries = true)
public void bulkUpdateProducts(List<ProductRequest> requests) {
// update many products
}allEntries = true carefully. It is simple and safe, but it can cause many future requests to miss the cache at once.Use @CachePut When You Want to Update the Cache
@CachePut always runs the method and stores the returned value in the cache.
import org.springframework.cache.annotation.CachePut;
@CachePut(value = "products", key = "#id")
public Product updateProductAndCache(Long id, ProductRequest request) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));
product.setName(request.name());
product.setPrice(request.price());
return productRepository.save(product);
}The difference is important: @Cacheable may skip the method, but @CachePut always executes it.
Conditional Caching
Spring also lets you cache only under certain conditions.
@Cacheable(
value = "products",
key = "#id",
condition = "#id != null",
unless = "#result == null"
)
public Product getProductById(Long id) {
return productRepository.findById(id).orElse(null);
}conditionis checked before the method runs.unlessis checked after the method runs.unless = "#result == null"avoids caching null results.
Caching Does Not Understand Business Consistency
One important interview point is that Spring caching does not magically understand your business consistency rules.
Spring only follows the cache annotations you configure. It does not know which pieces of data become stale after a write operation.
That means you must decide:
- when cached data should be evicted
- when cached data should be updated
- which cache keys are affected by a write
- whether a single entry or the whole cache should be cleared
For example, updating one product may affect:
- the product-by-id cache
- a product search cache
- a category listing cache
- a pricing cache
If only one cache entry is evicted while other related caches remain stale, users may receive inconsistent data.
Important Interview Follow-Ups
What is cached?
Usually method return values, based on the method arguments and cache key.
What is the key?
By default Spring builds a key from method arguments, but explicit keys are clearer in interviews.
What about stale data?
Cached values can become outdated if writes do not evict or update the cache.
Is this distributed?
Not automatically. A local in-memory cache is per application instance.
When use Redis?
Use Redis or another external cache when multiple app instances need shared cache state.
What about TTL?
TTL is usually configured in the cache provider, not directly on @Cacheable.
Local Cache vs Distributed Cache
A local cache is simple and fast, but each application instance has its own copy.
In a cluster with several Spring Boot instances, one server might evict or update its local cache while another server still has stale data.
A distributed cache such as Redis helps because all application instances use the same shared cache.