Why Your Spring Boot Redis Cache Never Expires: Hits, Misses, TTL and Eviction as a State Machine

October 1, 2026 · How It Actually Works: Real Systems as State Machines (part 15)

▶ Watch on YouTube & subscribe to The Stack Underflow

You add one annotation, @Cacheable, and the next call with the same argument skips your method and the database. Behind it, Spring computes a key, asks Redis, stores copies, and has to cope when data changes or Redis goes away. The video follows all of it as one hierarchical state machine: a Spring Boot app on AKS caching in Azure Managed Redis, from startup to one key’s life inside Redis. This page is the written companion.

The one-line version: with Spring Boot’s defaults, a cached entry never expires, and Azure’s default eviction policy never evicts it. Set a TTL.

Last verified against the Spring Framework, Spring Boot and Spring Data Redis docs, redis.io and Microsoft Learn: 1 October 2026. Scope: Spring Boot 4.1.1, Spring Framework 7.0.9, Spring Data Redis 4.1.1 with the Lettuce client, Azure Managed Redis, from AKS. Not covered: reactive caching, Redis Cluster internals, client-side caching.

The pegs

From the restaurant picture (memory aids, not facts): your database is the kitchen, and Redis is the warming shelf by the pass, where ready dishes wait.

WordMemory pegWhat it actually is
RedisThe warming shelf by the passAn in-memory store holding copies, not the truth
HitThe dish is already on the shelfThe key exists; the copy is returned and your method never runs
MissCook it, put a copy on the shelfThe method runs and its result is stored
TTLThe timer card on each plateA key’s time to live; when it runs out, the key expires
EvictionClearing old plates when the shelf is fullRemoving keys when memory reaches maxmemory
@CacheEvictThrowing a plate away: the recipe changedDeleting the copy after the data changes

Which Azure Redis?

Azure Cache for Redis is being retired: the Basic, Standard and Premium tiers on 30 September 2028 (remaining instances are disabled from 1 October 2028), and the Enterprise and Enterprise Flash tiers on 31 March 2027. In Azure’s public cloud, since 1 April 2026, only existing customers can still create Basic, Standard or Premium caches; in July 2026 Microsoft removed the block it had planned for existing customers in October 2026. Microsoft recommends moving to Azure Managed Redis now, and the video uses it.

Chapter 1: startup

  • Two starters. In Boot 4, spring-boot-starter-data-redis brings Lettuce, and spring-boot-starter-cache brings the cache auto-configuration. Put @EnableCaching on a configuration class; Spring Boot’s docs say to avoid adding it to the main application class.
  • The connection. Lettuce connects using the spring.data.redis.* properties, or localhost:6379 by default.
  • Azure Managed Redis. It uses port 10000, with TLS required by default, and caches use Microsoft Entra ID by default. It is clustered by default with the OSS cluster policy, which “requires your client library to support the Redis Cluster API”, so set Spring’s cluster nodes (spring.data.redis.cluster.nodes), or choose the Enterprise cluster policy, which “makes Azure Managed Redis look nonclustered to users”.
  • The cache manager. With caching enabled and Redis configured, Boot creates a RedisCacheManager, unless you defined your own CacheManager. Your beans get caching proxies.
  • Startup doesn’t empty Redis. Copies stored by earlier pods can still be on the shelf.

Chapter 2: one request through the proxy

bookService.findById(42) carries @Cacheable("books"), so the call goes through Spring’s caching proxy first. The proxy talks to Redis before your method runs, after it, or instead of it. This is cache-aside, run for you: look on the shelf first, cook only on a miss. In proxy mode, only calls coming in from outside the bean are intercepted; a self-call skips the cache.

Chapter 3: a read with @Cacheable

  1. The key. With one parameter, Spring’s default key is that parameter: 42. Redis keys are prefixed with the cache name, so the key is books::42 and two caches never share a key.
  2. GET. Redis replies with the stored bytes, or nothing.
  3. Hit. Spring turns the bytes back into a Book, with Java serialization (JdkSerializationRedisSerializer) by default, and returns it. Your method is never called. JSON serializers exist too, and Spring Data Redis warns against Java deserialization in untrusted environments. If the bytes no longer fit your class, say after a new version is deployed, deserialization throws, like any other cache error.
  4. Miss. Your method queries the database and returns a Book. If it throws, nothing is cached.
  5. Store. By default even a null result is cached (a null marker; cache-null-values defaults to true). An unless expression such as #result == null vetoes the store after the method runs; a false condition skips the cache entirely, even the lookup. Spring stores the copy with a SET, carrying the TTL if you set one. By default, entries never expire.

The stampede

The common informal name for many callers missing the same key at once, each one querying the database. By default Spring’s cache abstraction locks nothing. sync = true is, in Spring’s own words, “effectively a hint”, and Spring Data Redis’s default cache writer is lock-free, so it takes no lock. The opt-in locking writer keeps one lock key per cache in Redis (books~lock), so it works across pods, but locking “applies on the cache level, not per cache entry”: it blocks the whole cache, and by default the lock never expires.

Chapter 4: a write

The copy on the shelf doesn’t change when the kitchen changes the recipe; your code decides what happens to it.

  • @CacheEvict, the common choice. The method updates the database; after it completes successfully, the entry is deleted. If the method throws, nothing is evicted. The next read misses and loads the new value.
  • Order matters. Update the database first, then evict. Microsoft’s cache-aside guidance explains why: evict first, and a reader can miss in the gap, load the old value, and put it back.
  • A failed delete leaves the old plate until its timer runs out, or forever without one. That’s why a TTL is your safety net.
  • @CachePut always runs the method and writes its result into the cache, so readers see the new value at once, but only if it uses the same key (the book’s id, not the Book object) and returns exactly what reads cache. Spring strongly discourages putting @CachePut and @Cacheable on the same method.

Chapter 5: when Redis fails

Redis is a separate server over the network. By default, a cache error fails your request, even when the database is fine: Spring says any exception in a cache operation “is thrown back at the client”, and the default SimpleCacheErrorHandler rethrows.

  • Lettuce reconnects automatically. Meanwhile a command can wait up to its timeout: 60 seconds (Lettuce’s default) unless you set spring.data.redis.timeout. Then Spring Data Redis throws a connection-failure or query-timeout exception.
  • With a handler that only logs, such as LoggingCacheErrorHandler, a failed read counts as a miss and your method goes to the database; a failed store or delete is logged and the request carries on.
spring.cache.redis.time-to-live=10m
spring.data.redis.timeout=2s

Register the logging handler through a CachingConfigurer if a cache outage should degrade to database reads rather than errors. (The values above are examples, not recommendations.)

Chapter 6: inside Redis, one key’s life

The key books::42 can go four ways, and each time the app just sees a miss:

  • Delete. @CacheEvict removes it at once. A SET replaces the value and clears any old timer.
  • Expiry. When its TTL runs out, reads no longer see it. Redis removes expired keys passively, when a client touches one, and actively, by periodically testing a few random keys that have an expiration.
  • Eviction. When memory reaches maxmemory, the eviction policy picks keys. Open-source Redis, by default, has no memory limit on 64-bit systems and uses noeviction. Azure Managed Redis defaults to volatile-lru: only keys with a TTL are eligible, least recently used first. Redis’s docs: the volatile-xxx policies “behave like noeviction if no keys have an associated expiration”, meaning writes get an error while reads keep working.
  • Failover. If the primary fails, a replica is promoted. Replication is asynchronous, so a very recent write can be lost. For a cache that’s just a miss, and it’s why Redis is not your database of record.

The video also covers Spring Session: with the Spring Session Data Redis starter, the HttpSession lives in Redis, so any pod behind your AKS service can load it.

Patterns, and when not to cache

PatternWhat it isIn Spring
Cache-asideRead the cache, load on a miss, invalidate on write@Cacheable + @CacheEvict
Write-throughUpdate the store and the cache in the same write@CachePut comes closest
Write-behindThe caching system writes changes back to the storeNot provided: your method writes the database

Don’t cache sensitive or security-related data (especially in a shared cache), data that changes constantly or is rarely read after it’s written, or anything you can’t afford to lose. If reads must see a write at once, cache-aside can briefly serve a stale copy; Microsoft suggests write-through.

Pause & Prove

1. You add @Cacheable to findById with Spring Boot’s defaults, on Azure Managed Redis with its default eviction policy. When does books::42 leave Redis?

On its own, never. Boot sets no TTL, so the key never expires, and volatile-lru only evicts keys that have a TTL. It leaves only when something deletes it, such as @CacheEvict after an update, or, rarely, when a failover loses a very recent write. If memory fills and nothing can be evicted, new writes get an out-of-memory error. Set spring.cache.redis.time-to-live.

2. Azure Managed Redis hits its memory limit and none of your keys has a TTL. What happens to the next write?

  • The oldest key is evicted. Tempting, but under the default volatile-lru, keys without a TTL are never candidates, however old they are. (Under allkeys-lru, the least recently used key would go.)
  • A random key is evicted. That’s allkeys-random (or volatile-random among keys with a TTL), not the default.
  • The write gets an out-of-memory error. ✓ With no TTLs, volatile-lru behaves like noeviction: commands that add data get an error, reads keep working.
  • Redis restarts. The eviction docs describe an error on writes, not a restart.

3. Peg drill

Redis · hit · miss · TTL · eviction · @CacheEvict. (The warming shelf by the pass · the dish is already on the shelf · cook it, put a copy on the shelf · the timer card on each plate · clearing old plates when the shelf is full · throwing a plate away: the recipe changed.)

Before and after this video

Sources

Re-checked on 1 October 2026:

Change notes

  • 1 Oct 2026: first published.

Not affiliated with or endorsed by Redis Ltd., Microsoft, or VMware Broadcom (Spring). Found a mistake? Tell us in the video’s comments and we’ll correct this page.

Found this useful? The deep version lives on YouTube — new breakdowns of how AI dev tools actually work, weekly.

Subscribe on YouTube →

Prefer email? Get the free newsletter: one failure, traced step by step, about once a week.