Understanding Caching Strategies in Spring Applications
Course: Advanced Java Training
Topic: Understanding Caching Strategies in Spring Applications
Course Link: Advanced Java Training at JustAcademy
Demo Registration: Register for a Course Demo
Website: Visit JustAcademy
1. Introduction to Caching in Spring Applications
Caching is a performance optimization technique that stores frequently accessed data or previously computed results so that future requests can retrieve them without repeating expensive operations. In Spring applications, caching can reduce database queries, improve API response times, and lower the workload on external services.
For example, an e-commerce application may repeatedly retrieve product details, category lists, shipping rules, and configuration settings. Instead of querying the database for every request, the application can cache suitable results and reuse them until they expire or become invalid.
Spring provides a cache abstraction that lets developers define caching behavior with annotations and configuration. The underlying cache can be implemented with providers such as a local in-memory cache or Redis, depending on the application's requirements.
Why Caching Is Important
- Improved performance: Cached data can be returned faster than repeatedly retrieving it from a database or remote service.
- Reduced database load: Frequently repeated reads can be served from a cache.
- Better scalability: Reduced backend workload can help an application handle more requests.
- Lower external-service usage: Caching selected remote responses can reduce repeated network calls.
- Improved user experience: Faster responses can make applications feel more responsive.
- Cost optimization: Avoiding unnecessary database queries and external calls can reduce infrastructure usage.
2. How Caching Works in a Spring Application
Spring's cache abstraction intercepts eligible method calls and performs cache operations according to the configured annotations. The cache provider stores and retrieves the method results.
- A client sends a request to a Spring Boot application.
- The controller calls a service method annotated for caching.
- Spring checks the cache using the configured cache name and key.
- If the requested value exists, Spring returns the cached result without executing the method body.
- If the value is missing, the method executes and retrieves or calculates the result.
- Spring stores the eligible result in the cache.
- Subsequent requests using the same key may reuse the cached value.
Basic Architecture
Client Request
|
v
Spring Boot Controller
|
v
Service Method
|
v
Spring Cache Abstraction
|
+------ Cache Hit ------> Return Cached Result
|
+------ Cache Miss -----> Execute Method
|
v
Database / API
|
v
Cache Result
|
v
Return Data
3. Spring Cache Abstraction
Spring Cache provides a common programming model for caching. Developers can use the same annotations while changing the underlying cache provider through configuration. This reduces the need to write provider-specific cache operations throughout the service layer.
Main Spring Cache Annotations
| Annotation |
Purpose |
Typical Use Case |
@EnableCaching |
Enables annotation-driven caching. |
Application configuration. |
@Cacheable |
Checks the cache and stores an eligible result on a cache miss. |
Reading product or customer details. |
@CachePut |
Executes the method and updates the cache with its result. |
Refreshing cached data after an update. |
@CacheEvict |
Removes one or more entries from a cache. |
Invalidating data after deletion. |
@Caching |
Combines multiple cache operations on a method. |
Updating several related cache entries. |
@CacheConfig |
Defines shared cache settings for methods in a class. |
Specifying common cache names. |
4. Strategy One: Cache-Aside Pattern
The cache-aside pattern is one of the most common caching strategies. The application checks the cache first and retrieves data from the underlying data source when the requested entry is missing. After retrieval, the application stores the result in the cache for future requests.
How Cache-Aside Works
- Receive a request for a specific record.
- Check whether the record exists in the cache.
- If the record exists, return the cached value.
- If the record does not exist, query the database or external service.
- Store the result in the cache if it is suitable for caching.
- Return the result to the caller.
- Invalidate or refresh the cache when the underlying data changes.
Example Using @Cacheable
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id")
public Product getProductById(Long id) {
System.out.println("Fetching product from database");
return productRepository.findById(id)
.orElseThrow(() ->
new IllegalArgumentException("Product not found"));
}
}
On the first call for a product ID, the method executes and the result can be stored in the cache. A later call for the same ID can return the cached result without repeating the database lookup.
Advantages
- Simple to implement and understand.
- Only data that is requested needs to be loaded into the cache.
- The database remains the primary source of truth.
- Works well for read-heavy applications.
Limitations
- The first request after a cache miss is slower because it must load the data.
- Data can become stale if updates do not invalidate or refresh cached entries.
- Many simultaneous misses for the same key can overload the database.
5. Strategy Two: Read-Through Caching
In the read-through caching strategy, the application requests data from the cache layer, and the cache provider is responsible for loading missing data from the underlying data source through a configured loader. The application interacts primarily with the cache rather than explicitly implementing every cache-miss step.
How Read-Through Works
- The application requests a record from the cache.
- The cache checks whether the requested entry exists.
- If the entry exists, the cached value is returned.
- If the entry is missing, a configured loader retrieves the value from the underlying source.
- The retrieved value is stored in the cache and returned to the application.
Spring's cache abstraction supports cache loading patterns, but a complete read-through design depends on the selected cache provider and its integration. With simple @Cacheable methods, the service method itself usually loads the value on a cache miss.
Advantages
- Centralizes loading behavior around the cache layer.
- Can simplify repeated read operations.
- Helps maintain consistent cache-loading rules.
Limitations
- Requires suitable loader support or additional application configuration.
- Cache failures and source-data failures must be handled carefully.
- Invalidation is still necessary when source data changes.
6. Strategy Three: Write-Through Caching
In a write-through strategy, a write operation updates the primary data source and the cache as part of a coordinated write workflow. The purpose is to reduce the time during which the cache contains an older value after an update.
How Write-Through Works
- The application receives a request to create or update a record.
- The application writes the updated data to the primary data source.
- The cache is updated with the new value, or the previous cache entry is invalidated.
- Future reads can retrieve the current value from the cache, subject to the consistency guarantees of the implementation.
Example Using @CachePut
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
private final CustomerRepository customerRepository;
public CustomerService(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
@CachePut(value = "customers", key = "#result.id")
public Customer updateCustomer(Customer customer) {
return customerRepository.save(customer);
}
}
After a successful method execution, Spring places the returned customer in the cache using its ID. This example assumes the repository returns an entity with a valid ID. The database write and cache update are not automatically one atomic transaction, so production applications must account for transaction failures and possible cache inconsistency.
Advantages
- Can keep frequently accessed cache entries up to date after successful writes.
- Reduces the likelihood of serving an old value after a cache refresh.
- Works well when reads and writes both need predictable cache behavior.
Limitations
- Write operations may require additional processing.
- Database and cache updates can fail independently.
- Coordinating cache updates with database transactions requires deliberate design.
7. Strategy Four: Write-Behind or Write-Back Caching
In a write-behind strategy, the application first updates the cache, while the actual write to the primary data source is deferred and performed later by a background process. This can improve write throughput in systems designed to tolerate delayed persistence.
How Write-Behind Works
- The application receives a write request.
- The cache or associated write queue records the change.
- The application may acknowledge the request before the primary data source is updated.
- A background worker writes pending changes to the database.
- The worker tracks successful writes and retries failures where appropriate.
Advantages
- Can reduce write latency for suitable workloads.
- Can batch multiple changes to improve throughput.
- Can reduce immediate pressure on the primary database.
Risks and Limitations
- Data may be lost if pending writes are not durably recorded and the cache fails.
- Database state can temporarily lag behind cache state.
- Retries, ordering, conflict handling, and duplicate writes must be designed carefully.
- Not appropriate for every business operation, especially those requiring immediate durable persistence.
Write-behind caching is not automatically provided by simply adding @Cacheable or @CachePut. It generally requires a separate persistence queue, background worker, or cache-provider-specific capability.
8. Strategy Five: Refresh-Ahead Caching
Refresh-ahead caching updates frequently accessed cache entries before they expire. Rather than waiting for a request to encounter an expired or missing entry, the application refreshes the entry in advance.
Example Scenario
An application caches product recommendations for ten minutes. If those recommendations are frequently requested, a background task can refresh them before expiration so that users are less likely to experience a slow cache miss.
Advantages
- Can reduce latency for popular data.
- Can reduce simultaneous cache misses after expiration.
- Helps maintain availability of frequently requested entries.
Limitations
- Background refreshes may perform unnecessary work for entries that are no longer popular.
- Refresh tasks must avoid running concurrently for the same expensive entry.
- Failures need retry, fallback, and monitoring policies.
Refresh-ahead behavior normally requires scheduled tasks, provider-specific features, or a custom refresh mechanism. It is not enabled automatically by the basic Spring caching annotations.
9. Strategy Six: Time-to-Live (TTL) Caching
Time-to-live, commonly abbreviated as TTL, determines how long a cached entry remains valid before it expires. TTL is useful when an application can tolerate data being slightly outdated for a defined period.
| Data Type |
Illustrative TTL |
Reason |
| Product details |
5–15 minutes |
Product information may change occasionally. |
| Category lists |
30–60 minutes |
Category information may change less frequently. |
| Application configuration |
Several minutes to hours |
Depends on configuration update requirements. |
| Live inventory counts |
Seconds or no cache |
Rapidly changing values may need fresher reads. |
| Reference data |
Hours or longer |
Stable reference information can often be cached longer. |
These values are illustrative rather than universal recommendations. Select TTL values according to business freshness requirements, traffic patterns, update frequency, and the consequences of stale data.
Example Redis TTL Configuration
import java.time.Duration;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@Configuration
public class RedisCachingConfiguration {
@Bean
public CacheManager cacheManager(
RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration configuration =
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(configuration)
.build();
}
}
This example configures a ten-minute default TTL for caches managed by the configured Redis cache manager. It does not mean every Redis key created outside this cache manager receives the same expiration policy.
10. Strategy Seven: Local In-Memory Caching
Local caching stores data inside the memory of an individual application instance. It can provide very low-latency access because the application does not need a network call to retrieve the cached entry.
Common Java caching libraries include Caffeine, which provides features such as size-based eviction, expiration, and cache statistics.
Advantages
- Very low access latency.
- No separate cache server is required for basic local caching.
- Can reduce repeated work for frequently accessed data.
Limitations
- Each application instance maintains its own cache.
- Entries may differ between instances after an update.
- Cache contents usually disappear when the application restarts.
- Memory usage must be controlled.
Example with Caffeine
Add the Caffeine dependency to the project using a version compatible with the application's dependency management. Then configure a cache manager:
import java.util.concurrent.TimeUnit;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.github.benmanes.caffeine.cache.Caffeine;
@Configuration
@EnableCaching
public class LocalCacheConfiguration {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager =
new CaffeineCacheManager("products", "categories");
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES));
return manager;
}
}
This configuration creates local caches named products and categories, with a maximum size of 1,000 entries per cache and expiration ten minutes after a write. The cache is local to the application instance.
11. Strategy Eight: Distributed Caching with Redis
Distributed caching stores entries in a cache service shared by multiple application instances. Redis is a common choice for Spring Boot applications that need a central cache layer.
Advantages
- Multiple application instances can reuse shared cache entries.
- Cache entries can remain available when an individual application instance restarts, subject to Redis configuration.
- Can support horizontally scaled services.
- Redis offers expiration, data structures, and operational monitoring capabilities.
Limitations
- Network calls add latency compared with local memory.
- Redis availability and capacity become important operational concerns.
- Concurrent updates and invalidation require careful design.
- Serialization settings must remain compatible across application versions.
Basic Redis Configuration in Spring Boot
Add the following dependencies to a Maven project:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Configure the Redis connection in application.properties:
spring.data.redis.host=localhost
spring.data.redis.port=6379
Enable caching on the Spring Boot application or a configuration class:
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
org.springframework.boot.SpringApplication.run(
Application.class, args);
}
}
Use a Redis-backed cache manager when the application requires custom TTLs, serialization, or per-cache settings. In production, use secure Redis connectivity, appropriate authentication, and a deliberate availability strategy.
12. Choosing the Right Cache Key
A cache key identifies a specific cached value. A good key should be stable, unique for the requested data, and include every parameter that can affect the result.
Examples of Cache Keys
products::101
customers::205
categories::electronics
product-search::electronics::page-1
customer-orders::customer-205
Example with Multiple Parameters
@Cacheable(
value = "product-search",
key = "#category + ':' + #page + ':' + #size"
)
public ProductPage searchProducts(
String category, int page, int size) {
return productRepository.search(category, page, size);
}
The key includes category, page number, and page size because each can change the returned result. If the response also depends on tenant, locale, currency, or access permissions, those factors may need to be included in the key as well.
Key Design Best Practices
- Use meaningful prefixes for different data types.
- Include all result-affecting parameters.
- Prefer stable identifiers to mutable display names.
- Avoid embedding passwords, access tokens, or secrets in keys.
- Use tenant-specific key components where data is separated by tenant.
- Consider version prefixes when the structure of cached values changes between releases.
13. Conditional Caching
Not every method result should be cached. Spring provides condition and unless attributes to control when caching applies.
13.1 Using condition
@Cacheable(
value = "products",
key = "#id",
condition = "#id != null"
)
public Product getProduct(Long id) {
return productRepository.findById(id).orElse(null);
}
The method is cached only when the condition is true. If the ID is null, Spring does not apply this cache operation and the method executes normally.
13.2 Using unless
@Cacheable(
value = "products",
key = "#id",
unless = "#result == null"
)
public Product getProduct(Long id) {
return productRepository.findById(id).orElse(null);
}
The unless expression is evaluated after the method returns. In this example, null results are not added to the cache. This can help avoid storing missing records, although negative caching may be useful in some applications when configured with an appropriate short TTL.
14. Cache Invalidation Strategies
Cache invalidation removes or refreshes cached data when the underlying data changes. Incorrect invalidation is a common reason applications return outdated results.
14.1 Invalidate One Entry
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
After a successful method invocation, the corresponding cache entry is evicted. A subsequent read can retrieve fresh data from the underlying source.
14.2 Clear an Entire Cache
@CacheEvict(value = "products", allEntries = true)
public void refreshProductCatalog() {
// Rebuild or refresh product catalog data.
}
Clearing the entire cache can be useful after a bulk update, but it may cause a large number of cache misses. Use it selectively and consider targeted eviction when possible.
14.3 Event-Driven Invalidation
In a microservices architecture, one service may update data that another service caches. The updating service can publish an event, and interested services can invalidate or refresh their affected entries.
ProductUpdated Event
|
v
Message Broker
|
v
Cache-Using Service
|
v
Evict or Refresh Cache Entry
Event-driven invalidation reduces the need for every service to access another service's internal cache directly. However, events may be delayed or delivered more than once, so consumers should handle duplicate events and temporary inconsistencies safely.
15. Cache Stampede and Thundering Herd
A cache stampede occurs when a popular entry expires or is removed and many requests try to reload it at nearly the same time. The resulting burst of database queries can reduce performance or overload the underlying system.
Ways to Reduce Cache Stampedes
- Use synchronized cache loading where the selected provider supports it.
- Apply randomized expiration times to avoid many entries expiring together.
- Refresh popular entries before they expire.
- Use request coalescing so concurrent requests share a single reload operation.
- Apply database connection limits and request rate controls.
- Use stale-while-revalidate approaches when the business can tolerate temporarily stale data.
Example of Synchronized Loading
@Cacheable(value = "products", key = "#id", sync = true)
public Product getProduct(Long id) {
return productRepository.findById(id)
.orElseThrow(() ->
new IllegalArgumentException("Product not found"));
}
The sync attribute requests synchronized loading for a cache key, but the exact behavior depends on cache-provider support and implementation constraints. It should not be assumed to provide a universal distributed lock across all application instances.
16. Cache Warming
Cache warming is the process of loading selected data into the cache before it is requested by users. It is useful when certain records are predictable, popular, or important immediately after an application starts.
Common Cache-Warming Examples
- Load frequently accessed category lists during application startup.
- Preload configuration and reference data.
- Warm popular product entries before a large promotion.
- Refresh frequently requested dashboard summaries at scheduled intervals.
Things to Consider
- Avoid loading the entire database into memory without a clear reason.
- Keep startup time within operational limits.
- Handle failures when the database or cache is unavailable.
- Use batch operations carefully and avoid overwhelming downstream services.
17. Cache Consistency and Database Transactions
A cache is often a copy of data stored elsewhere. The application must define how fresh the cached copy needs to be and what should happen if the database and cache disagree.
Common Consistency Problems
- The database update succeeds, but cache invalidation fails.
- The cache is updated before the database transaction commits.
- Two application instances update the same record concurrently.
- An invalidation event is delayed or lost.
- A stale value is repopulated after a newer value has been written.
Recommended Practices
- Treat the primary database as the source of truth when appropriate.
- Define acceptable staleness for each cached data type.
- Coordinate cache operations with database transaction boundaries.
- Use after-commit processing when cache updates must follow successful transactions.
- Use versioning, idempotency, or event-driven invalidation for cross-service updates.
- Monitor failed evictions and cache refresh operations.
Spring cache annotations do not automatically create a distributed transaction between a database and Redis. If reliable event publication is also required, consider a transactional outbox pattern for coordinating database changes and event creation.
18. Common Caching Mistakes
| Mistake |
Why It Is a Problem |
Better Approach |
| Caching every method |
Some results are inexpensive, frequently changing, or user-specific. |
Cache selectively based on measured cost and access patterns. |
| Using an incorrect cache key |
Different requests may receive the same cached result. |
Include all parameters that affect the result. |
| Never expiring entries |
Stale data may remain indefinitely. |
Use suitable TTLs and explicit invalidation. |
| Ignoring update operations |
Cached values may not reflect changes to the database. |
Refresh or evict affected entries after successful updates. |
| Ignoring cache stampedes |
Many concurrent misses can overload the database. |
Use synchronized loading, refresh-ahead, or request coalescing. |
| Assuming Redis never fails |
Cache outages may affect request latency and backend load. |
Design, monitor, and test an explicit failure strategy. |
| Mixing incompatible serializers |
Application instances may be unable to read each other's cached values. |
Standardize serialization and plan for schema changes. |
| Caching sensitive information carelessly |
Confidential data may be exposed through cache access or logs. |
Apply access controls, data minimization, and secure connectivity. |
19. Measuring Cache Performance
Caching should be evaluated with metrics rather than assumptions. A cache can add complexity without providing much benefit if the data is rarely reused or the original operation is inexpensive.
| Metric |
Meaning |
Why It Matters |
| Cache hit rate |
Percentage of lookups served from the cache. |
Indicates how often the cache avoids backend work. |
| Cache miss rate |
Percentage of lookups that require the underlying method or data source. |
Helps identify ineffective cache keys or short TTLs. |
| Response latency |
Time taken to complete a request. |
Shows whether caching improves user-facing performance. |
| Database query volume |
Number of queries sent to the database. |
Measures how much work caching removes from the database. |
| Cache memory usage |
Memory consumed by cached entries. |
Helps prevent memory pressure and eviction problems. |
| Eviction rate |
Frequency with which entries are removed by expiration or capacity policies. |
Can reveal undersized caches or unsuitable TTL settings. |
| Error rate |
Cache access or refresh failures. |
Helps identify connectivity or serialization issues. |
Performance Testing Checklist
- Measure response time before enabling caching.
- Measure response time after caching.
- Compare database query counts.
- Test both cache-hit and cache-miss scenarios.
- Test high-concurrency requests for the same key.
- Test TTL expiration and invalidation behavior.
- Observe performance when Redis or another cache provider is unavailable.
20. Testing Spring Cache Behavior
Testing should confirm that cache operations work correctly and that application behavior remains correct when data changes or the cache is unavailable.
Testing Scenarios
- Call a cacheable service method for the first time and confirm that the underlying repository is invoked.
- Call the same method with the same key and confirm that the cached value is reused.
- Call the method with a different key and confirm that the correct record is returned.
- Update a record and verify that the cache is refreshed or invalidated.
- Delete a record and verify that stale cached data is not returned.
- Test expiration using a short TTL.
- Test null results and invalid input.
- Test behavior when the cache server is unavailable.
Example Test Concept
// Pseudocode for a service-level cache test:
//
// 1. Call getProduct(101).
// 2. Call getProduct(101) again.
// 3. Verify that the repository was called only once.
// 4. Evict the cache entry for product 101.
// 5. Call getProduct(101) again.
// 6. Verify that the repository was called a second time.
Use integration tests with a real cache provider, such as Redis running in a test container, when testing provider-specific expiration, serialization, or connection behavior. A mocked cache alone may not reproduce these details.
21. Practical Project: Caching a Product Catalog
Project Objective: Build a Spring Boot REST API that retrieves product data from a database and uses a configurable cache strategy to improve performance.
Functional Requirements
- Create a product model with ID, name, price, and category.
- Create a repository for database operations.
- Implement a service method using
@Cacheable.
- Configure Redis or a local in-memory cache.
- Set an appropriate TTL for product data.
- Implement updates using
@CachePut or targeted eviction.
- Implement deletion using
@CacheEvict.
- Add validation and appropriate API error handling.
- Write tests for cache hits, misses, expiration, updates, and deletes.
- Measure the impact of caching on latency and database query volume.
Suggested Project Architecture
Client
|
v
Spring Boot REST Controller
|
v
Product Service
|
v
Spring Cache Abstraction
|
+------ Cache Hit ------> Return Product
|
+------ Cache Miss -----> Product Repository
|
v
Database
|
v
Cache Result
Expected Learning Outcomes
- Understand common caching strategies.
- Configure and use Spring's caching annotations.
- Compare local caching with distributed Redis caching.
- Design cache keys and TTL policies.
- Handle invalidation, consistency, and cache failures.
- Measure performance and verify caching behavior through tests.
22. Interview Questions and Answers
Q1. What is caching?
Answer: Caching stores frequently accessed or previously computed data so that future requests can retrieve it without repeating an expensive operation.
Q2. What is Spring Cache?
Answer: Spring Cache is an abstraction that provides annotation-driven caching operations and can integrate with different cache providers.
Q3. What is the cache-aside strategy?
Answer: The application checks the cache first, loads data from the underlying source on a cache miss, and stores the result for future requests when appropriate.
Q4. What is read-through caching?
Answer: In read-through caching, the cache layer or its loader retrieves missing values from the underlying data source and returns the result to the application.
Q5. What is write-through caching?
Answer: Write-through caching coordinates a write to the primary data source with an update to the cache so that cached values can reflect successful changes.
Q6. What is write-behind caching?
Answer: Write-behind caching defers persistence to the primary data source, usually through a background worker. It can improve write throughput but introduces durability and consistency concerns.
Q7. What is refresh-ahead caching?
Answer: Refresh-ahead caching refreshes selected entries before they expire to reduce cache misses and latency for frequently accessed data.
Q8. What is a cache TTL?
Answer: TTL stands for time-to-live. It specifies how long a cache entry remains available before it expires.
Q9. What is the difference between local and distributed caching?
Answer: Local caching stores data in one application's memory, while distributed caching stores data in a shared cache service that multiple application instances can access.
Q10. What is the purpose of @Cacheable?
Answer: It checks the configured cache before method execution and returns a cached result when available. Otherwise, it executes the method and caches an eligible result.
Q11. What is the difference between @CachePut and @CacheEvict?
Answer: @CachePut executes a method and updates a cache entry with its result. @CacheEvict removes a cache entry or clears a cache.
Q12. What is cache invalidation?
Answer: Cache invalidation removes or refreshes cached data when the underlying source changes, reducing the chance of returning stale values.
Q13. What is a cache stampede?
Answer: A cache stampede occurs when many requests try to reload the same entry simultaneously after it expires or becomes unavailable.
Q14. How can cache consistency be improved?
Answer: Use appropriate TTLs, targeted invalidation, transaction-aware cache operations, event-driven invalidation, and stable versioning where needed.
Q15. When should caching be avoided?
Answer: Avoid caching when data changes too frequently, stale values are unacceptable, results are inexpensive to calculate, or safe cache invalidation cannot be achieved without adding more complexity than the performance benefit justifies.
23. Practice Exercises
- Explain cache-aside, read-through, write-through, write-behind, and refresh-ahead caching.
- Implement
@Cacheable for a product service.
- Configure a Redis cache with a ten-minute TTL.
- Configure a local Caffeine cache and compare it with Redis.
- Implement targeted cache eviction after a product update.
- Create a cache key that includes multiple method parameters.
- Use
condition and unless to control caching behavior.
- Simulate cache expiration and measure the impact on database queries.
- Explain how a cache stampede can occur and suggest mitigation strategies.
- Design an invalidation strategy for data updated by another microservice.
24. Quick Revision Checklist
- Understand why caching is used in enterprise applications.
- Know the differences between common caching strategies.
- Understand the role of Spring's cache abstraction.
- Know how to use
@Cacheable, @CachePut, and @CacheEvict.
- Design cache keys that represent all result-affecting inputs.
- Choose TTL values based on freshness requirements.
- Understand local caching and distributed caching with Redis.
- Plan for invalidation, transaction boundaries, and stale data.
- Recognize cache stampedes and apply appropriate safeguards.
- Test cache behavior and monitor hit rates, latency, memory, and errors.
25. Conclusion
Caching is an important performance technique for Spring applications, but selecting the right strategy depends on the workload, data freshness requirements, consistency needs, and operational environment. Cache-aside is a common starting point, while read-through, write-through, write-behind, refresh-ahead, local caching, and distributed caching solve different problems.
Spring's cache abstraction simplifies implementation through annotations, while providers such as Redis and Caffeine supply the underlying storage and cache behavior. Effective caching also requires careful key design, expiration policies, invalidation, failure handling, monitoring, and testing. A well-designed caching strategy reduces unnecessary backend work without compromising application correctness.
Continue Learning: Explore the Advanced Java Training course, register for a course demo, or visit the JustAcademy website.