Redis cache TTL expires after a write; time-to-idle refreshes on eligible reads and needs an explicit Redis and client contract.
Spring Redis cache TTL versus idle expiry: GETEX changes the read contract
Pick the clock before the annotation
A quote lookup cached for 47 seconds should expire 47 seconds after its last write if it is allowed to become stale regardless of traffic. That is TTL. A session-like lookup that stays alive while actively read needs time-to-idle. Spring Data Redis can opt into idle expiry through Redis GETEX, which reads and resets expiry in one operation. GETEX requires Redis 6.2 or newer. A normal GET from another client does not refresh that idle clock. If one code path uses Spring Cache and another reads Redis directly, the observed lifetime changes with the route taken.
Keep the refresh boundary visible
Configure one cache by name and give it a finite entry TTL. Turn on idle behavior only when every read path participates in the same rule. A cache hit is not an authorization check: include tenant identity in the key and still check the caller's rights. If a quote must become invalid immediately after a price edit, use eviction or a versioned key; waiting for either expiry clock is a business decision.
Test time without a production outage
Write a value, advance beyond the configured TTL, and assert a miss. For idle behavior, read before expiry, advance again, and assert the entry survives only when GETEX was used. Run the test against the actual Redis version, since an in-memory fake cannot establish command support. This is a configuration sketch, not a live Redis test.
Implementation sketch
RedisCacheConfiguration quotes = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(47))
.disableCachingNullValues();
// For a separate cache with idle semantics:
RedisCacheConfiguration activeQuotes = quotes.enableTimeToIdle();Cost and verification
A cache read is one Redis round trip, but idle refresh writes expiry metadata on each GETEX hit. Hot keys stay resident; size and eviction pressure can rise. Expiry does not coordinate concurrent misses across replicas.
Common Mistakes
- Do not describe TTL as resetting after every cache hit.
- Do not enable time-to-idle against a Redis server that lacks GETEX.
- Do not assume a raw GET refreshes an entry's idle expiry.
Read next
Spring cache keys: separate tenants and test the loader count, Spring cache eviction: invalidate the same tenant key used by the read, Spring @Cacheable sync: collapse a local same-key cache miss, Spring Session Redis across replicas: shared login state has a Redis failure boundary.
