How do you configure TTL, key prefixing, null handling, and serialization for a Redis-backed cache?
answer
- immutable — keep the returned config
- entryTtl default ZERO = no expiry
- value serializer JDK->Jackson JSON
- disableCachingNullValues vs penetration
- prefix cacheName:: namespaces + enables clear()
basics
~10 sYou build a RedisCacheConfiguration and set options on it: entryTtl(Duration) for expiry, a value serializer (e.g. JSON), disableCachingNullValues(), and a key prefix. You pass it to RedisCacheManager as the default and/or per cache.
solid answer
~30 sRedisCacheConfiguration is an immutable, fluent value object describing how one (or all) caches behave. Key knobs: entryTtl(Duration) sets per-entry TTL (default: no expiry); serializeValuesWith(SerializationPair) swaps the default JDK serializer for e.g. GenericJackson2JsonRedisSerializer so values are human-readable JSON and types needn't be Serializable; serializeKeysWith(...) usually stays StringRedisSerializer; computePrefixWith(...) / prefixCacheNameWith(...) control the key prefix (default cacheName::); disableCachingNullValues() makes the cache reject nulls instead of storing a placeholder; disableKeyPrefix() removes prefixing (risky — key collisions across caches). Because it's immutable, each with*/serialize* call returns a new instance, so chain them. You register it via RedisCacheManager.builder(cf).cacheDefaults(defaultCfg).withInitialCacheConfigurations(perCacheMap).build().
code
java · 19 lines@Bean
RedisCacheManager cacheManager(RedisConnectionFactory cf) {
RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.disableCachingNullValues()
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
Map<String, RedisCacheConfiguration> perCache = Map.of(
"sessions", defaults.entryTtl(Duration.ofSeconds(30)),
"reference", defaults.entryTtl(Duration.ofHours(24)));
return RedisCacheManager.builder(cf)
.cacheDefaults(defaults)
.withInitialCacheConfigurations(perCache)
.build();
}go deeper
Know entryTtl sets expiry and that you can switch values to JSON.
Configure TTL, key/value serializers, null handling, and per-cache overrides via the builder; understand immutability.
Weigh JSON @class coupling, null-caching vs penetration, prefix semantics and their effect on clear().
Set org-wide serialization/TTL conventions, handle schema evolution of cached payloads, and decide property-based vs bean-based config across services.
**RedisCacheConfiguration is immutable.** Every mutator (`entryTtl`, `serializeValuesWith`, `disableCachingNullValues`, `computePrefixWith`, …) returns a *new* `RedisCacheConfiguration`; the original is unchanged. So you must keep the returned value — `cfg.entryTtl(...)` alone does nothing if you ignore the result. Start from `RedisCacheConfiguration.defaultCacheConfig()`. **TTL / expiry.** `entryTtl(Duration.ofMinutes(10))` makes `RedisCache` write values with a Redis `PX`/expiry so they auto-evict. The default is `Duration.ZERO` meaning **no expiry** — entries live forever, a common memory-leak footgun. Since Spring Data Redis 3.2 there is also `entryTtl(RedisCacheWriter.TtlFunction)` for per-entry/dynamic TTL. **Serialization.** Two `SerializationPair`s: one for keys, one for values. - Keys default to `StringRedisSerializer` (readable keys like `books::978-1`). Keep this. - Values default to `JdkSerializationRedisSerializer` — binary, and it requires cached types to implement `java.io.Serializable`. The common upgrade is `serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))`, giving JSON values that are debuggable and cross-language, and dropping the `Serializable` requirement. Trade-off: Jackson embeds `@class` type info by default (for polymorphic deserialization), so the stored JSON is coupled to your class names/packages — renaming a class can break existing cached entries. **Null values.** By default the cache *stores nulls* (as an internal placeholder) so that a `@Cacheable` method returning `null` still short-circuits the DB on the next call (cache penetration protection). `disableCachingNullValues()` flips this: nulls are not cached, and — importantly — attempting to cache a null then throws, because the cache can no longer represent 'we looked and found nothing'. **Key prefix.** Default prefix is `CacheKeyPrefix.simple()` → `cacheName + "::"`. This namespaces caches so `orders::42` and `users::42` don't collide in the shared keyspace and so `clear()` can delete just one cache's keys. `computePrefixWith(cacheName -> "app:" + cacheName + ":")` customizes it; `prefixCacheNameWith("app:")` prepends a global prefix; `disableKeyPrefix()` removes it entirely — only safe if every cache uses globally unique keys. **Per-cache overrides.** `cacheDefaults(cfg)` applies to every cache; `withInitialCacheConfigurations(Map<String, RedisCacheConfiguration>)` overrides specific named caches (e.g. short TTL for `sessions`, long TTL for `reference-data`). `withCacheConfiguration(name, cfg)` on the builder does one at a time. **Other flags.** `disableCreateOnMissingCache()` makes the manager reject cache names not declared up front (fail fast). `enableTimeToIdle()` (3.2+) treats TTL as idle-time, refreshing expiry on read via `GETEX`. **Boot property shortcut.** For the *default* cache only you can skip Java config: `spring.cache.redis.time-to-live=10m`, `spring.cache.redis.key-prefix`, `spring.cache.redis.cache-null-values`, `spring.cache.redis.use-key-prefix`. Per-cache differences still need a `RedisCacheManagerBuilderCustomizer` or an explicit bean.
- Why might disableCachingNullValues() hurt you?It removes protection against cache penetration: if a method legitimately returns null (missing row), every call re-hits the datastore because 'absence' is never cached. Keep null caching when nulls are frequent and lookups are expensive.
- What breaks if you use GenericJackson2JsonRedisSerializer and later rename the cached class?By default it stores the fully-qualified class name in an @class field. Renaming/moving the class makes existing cached JSON fail to deserialize until those entries expire or are evicted.
saying these in an interview costs you the question
- Calling cfg.entryTtl(...) and discarding the result — the config is immutable, so nothing changes
- Assuming Redis caches expire by default (default TTL is ZERO = never)
- Thinking disableKeyPrefix() is harmless (it invites cross-cache key collisions and breaks per-cache clear())
- Believing JSON serialization removes all coupling (Jackson still embeds @class type info by default)