Caching with @Cacheable
Spring's caching abstraction lets a single annotation — @Cacheable — remember an expensive method's result, while @CacheEvict keeps it fresh and providers like Caffeine or Redis do the storing.
Learn Caching with @Cacheable in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Before You Start
You should understand Spring beans and services , and be comfortable with maps and lambdas for the memoization demos.
What You'll Learn
The Big Idea — A Sticky Note on Your Monitor
💡 Analogy: Imagine someone keeps asking you for the office Wi-Fi password. The first time you walk to IT and fetch it (the expensive call); then you scribble it on a sticky note on your monitor. Every later request is answered instantly from the note — that's @Cacheable . But when IT changes the password, the note is now wrong , so you must tear it down and write the new one: that's @CacheEvict . The note can live on your own desk ( Caffeine , fast but only yours) or on a shared whiteboard everyone can see ( Redis , slower to reach but shared by the whole team).
Fetch the slow answer once, reuse it cheaply — and remember to update the note when the truth changes.
1️⃣ The Core — Remember Results by Key
@Cacheable is memoization: store a method's result keyed by its arguments so the expensive work happens once per key. The plain-Java demo uses computeIfAbsent to make that mechanism explicit.
2️⃣ Staying Correct — Eviction
A cache speeds reads but can go stale . When the source data changes you must remove the old entry — that's @CacheEvict . Forget it and readers keep seeing the outdated value.
3️⃣ The Spring Way — @EnableCaching + Annotations
Turn caching on once with @EnableCaching , then annotate methods: @Cacheable to read-through, @CacheEvict to invalidate, @CachePut to refresh. Spring applies the logic via AOP proxies.
4️⃣ Choosing a Provider — Caffeine vs Redis
Spring caches against whatever provider is on the classpath. Caffeine is a fast in-process cache for a single instance; Redis is a distributed cache shared across instances and surviving restarts.
🧠 Quick Recall
Answer: no — it's a cache hit, so the stored value is returned and the body is skipped.
Answer: @CacheEvict (or @CachePut ) on the write removes/refreshes the affected entry.
Answer: Redis — a distributed cache. Caffeine is local to a single JVM.
🎯 YOUR TURN — Memoized Fibonacci
Complete fib(n) using the cache map so each n is computed only once.
🧩 MINI-CHALLENGE — Read-Through Cache with Eviction
Implement get (cache-through) and set (update + evict) and prove the read after a set is fresh.
Common Errors
- ❌ Forgetting @EnableCaching. The annotations do nothing. Fix: add it to a config class.
- ❌ Not evicting on update. Readers see stale data. Fix: add @CacheEvict / @CachePut on writes.
- ❌ Self-invocation calls. Calling a cached method from within the same bean bypasses the proxy. Fix: call via the injected bean.
- ❌ Wrong key. Too coarse a key returns wrong results for different inputs. Fix: use a precise SpEL key.
- ❌ Unbounded cache. Memory grows forever. Fix: set a maximum size and TTL.
- ❌ Caching cheap or volatile methods. No benefit, added risk. Fix: cache expensive, stable, repeated calls.
Pro Tips
- 💡 Set a TTL and max size (e.g. Caffeine's expireAfterWrite , maximumSize ) to bound memory and staleness.
- 💡 Pick keys deliberately with SpEL, e.g. key = "#book.isbn" , to avoid collisions.
- 💡 Use Redis when you scale out so every instance shares one consistent cache.
- 💡 Measure first: cache the genuinely hot, expensive paths shown by your metrics — not everything.
📋 Quick Reference
Concept
Syntax / idea
Note
enable
@EnableCaching
On a config class
read-through
@Cacheable("books")
Skip body on hit
key
key = "#book.isbn"
SpEL, default = args
invalidate
@CacheEvict
Remove stale entry
refresh
@CachePut
Run + update cache
clear all
@CacheEvict(allEntries=true)
Whole cache
in-process
Caffeine
Fast, single node
distributed
Redis
Shared, survives restart
mechanism
AOP proxy
Intercepts the call
risk
stale data
Evict + TTL
📚 Resources / Keep Learning
- 📘 Cache Abstraction — Spring Framework reference
- 📗 Caching — Spring Boot reference
- 📗 Caffeine cache — project wiki
❓ Frequently Asked Questions
🎉 Lesson Complete!
You now understand how @Cacheable memoizes results by key, how keys are derived, how @CacheEvict and @CachePut keep data fresh, how @EnableCaching turns it all on, and when to choose Caffeine versus Redis.
Next up: Containerizing a Spring Boot App — Dockerfiles, layered jars, and buildpacks.
Practice quiz
What does @Cacheable do?
- Deletes a cache
- Stores a method's result so subsequent calls with the same key return the cached value
- Encrypts the result
- Runs the method twice
Answer: Stores a method's result so subsequent calls with the same key return the cached value. @Cacheable caches the return value keyed by the method arguments; later matching calls skip the method body and return the cached value.
Which annotation enables Spring's caching abstraction?
- @EnableCaching
- @StartCache
- @CacheOn
- @Caching
Answer: @EnableCaching. You add @EnableCaching to a configuration class to turn on Spring's annotation-driven caching.
What forms the default cache key for a @Cacheable method?
- The method name
- The current time
- The method's parameters
- A random UUID
Answer: The method's parameters. By default the key is derived from the method arguments; you can customise it with a SpEL key expression.
What does @CacheEvict do?
- Adds an entry
- Reads an entry
- Locks the cache
- Removes one or all entries from a cache (e.g. after an update)
Answer: Removes one or all entries from a cache (e.g. after an update). @CacheEvict removes entries so stale data is purged — for example when the underlying record changes.
When a @Cacheable method is called a SECOND time with the same arguments...
- The method body runs again
- The cached value is returned and the body is skipped
- An exception is thrown
- The cache is cleared
Answer: The cached value is returned and the body is skipped. A cache hit returns the stored value without executing the method body — that is the whole point.
Which of these is a common cache provider used with Spring?
- Caffeine
- Tomcat
- Jackson
- Log4j
Answer: Caffeine. Caffeine is a popular high-performance in-memory cache; Redis is common for distributed caching.
Why might you choose Redis over an in-memory cache like Caffeine?
- It is simpler to set up
- It needs no network
- It is a distributed cache shared across multiple app instances
- It is always faster
Answer: It is a distributed cache shared across multiple app instances. Redis is an external, shared cache, so multiple application instances see the same cached data — useful when scaling out.
What does @CachePut do?
- Always skips the method
- Deletes the cache
- Disables caching
- Always executes the method and updates the cache with the result
Answer: Always executes the method and updates the cache with the result. @CachePut runs the method every time and stores its result, keeping the cache in sync without short-circuiting.
A risk of caching is...
- Slower reads
- Serving stale data if entries aren't evicted/expired correctly
- Higher network usage always
- It breaks compilation
Answer: Serving stale data if entries aren't evicted/expired correctly. Caches can return outdated values; correct eviction, TTLs, and invalidation keep cached data fresh.
Spring's caching is implemented via...
- bytecode rewriting at runtime by the JVM
- proxies/AOP that intercept the method call
- a separate thread per cache
- database triggers
Answer: proxies/AOP that intercept the method call. Spring uses AOP proxies to intercept calls to cacheable methods and apply the cache logic around them.
Continue this course
- Previous: Monitoring with Actuator
- Next: Containerizing a Spring Boot App