Summary
In Spring Data Redis 4.x, RedisCacheWriter.put() now defaults to
asynchronous (fire-and-forget) writes when a ReactiveRedisConnectionFactory
(e.g., Lettuce) is present. This is a silent behavioral change from 3.x where
writes were always synchronous/blocking.
There is no mention of this change in the migration guide, making it very
difficult to diagnose cache inconsistencies after upgrading.
Environment
- Spring Data Redis: 3.x → 4.x
- Redis driver: Lettuce (ReactiveRedisConnectionFactory)
Problem
What changed internally
In DefaultRedisCacheWriter, the constructor now accepts an asynchronousWrites
flag, which defaults to true when a ReactiveRedisConnectionFactory is
detected:
// DefaultRedisCacheWriter.java (4.x)
if (REACTIVE_REDIS_CONNECTION_FACTORY_PRESENT
&& connectionFactory instanceof ReactiveRedisConnectionFactory) {
this.asyncCacheWriter = new AsynchronousCacheWriterDelegate();
this.asynchronousWrites = asynchronousWrites; // defaults to true via nonLockingRedisCacheWriter
}
// put() in 4.x — fire-and-forget when asynchronousWrites = true
if (writeAsynchronously()) {
asyncCacheWriter.store(name, key, value, ttl)
.thenRun(() -> statistics.incPuts(name)); // no error handling
} else {
execute(name, connection -> { doPut(...); return "OK"; });
}
In 3.x, put() was always a blocking call. There was no async path.
Real-world impact: cache warmup in batch applications
A common pattern is running a short-lived batch application to pre-warm the
cache before the main application starts:
Batch app starts
→ calls @Cacheable method
→ transaction commits
→ afterCommit() fires → cacheWriter.put()
→ async write is scheduled (fire-and-forget)
Batch app finishes → Spring context closes → Lettuce Netty event loop shuts down
→ in-flight async write is dropped
→ cache entry never reaches Redis
Main app starts → cache miss on every request
This behavior is non-deterministic: if the async write happens to complete
before the context closes, the cache is populated. Otherwise, it silently fails.
No exception is thrown. No log is emitted.
After upgrading from 3.x to 4.x, our cache warmup batch stopped working
intermittently — with no error in sight.
Workaround
The immediateWrites() option on RedisCacheWriterConfigurer restores
synchronous write behavior:
RedisCacheWriter writer = RedisCacheWriter.create(connectionFactory,
config -> config.immediateWrites());
RedisCacheManager manager = RedisCacheManagerBuilder
.fromCacheWriter(writer)
.build();
This works, but:
- It is not mentioned in the migration guide from 3.x to 4.x
- The API surface of
RedisCacheWriterConfigurer is not easy to discover
- Users migrating from 3.x have no reason to look for this option
Suggestion
- Add a note to the migration guide explaining that writes are now
asynchronous by default when using a reactive driver, and document
immediateWrites() as the opt-in for the previous behavior.
- Log a warning when an async write fails (instead of silently dropping it).
Summary
In Spring Data Redis 4.x,
RedisCacheWriter.put()now defaults toasynchronous (fire-and-forget) writes when a
ReactiveRedisConnectionFactory(e.g., Lettuce) is present. This is a silent behavioral change from 3.x where
writes were always synchronous/blocking.
There is no mention of this change in the migration guide, making it very
difficult to diagnose cache inconsistencies after upgrading.
Environment
Problem
What changed internally
In
DefaultRedisCacheWriter, the constructor now accepts anasynchronousWritesflag, which defaults to
truewhen aReactiveRedisConnectionFactoryisdetected:
In 3.x,
put()was always a blocking call. There was no async path.Real-world impact: cache warmup in batch applications
A common pattern is running a short-lived batch application to pre-warm the
cache before the main application starts:
This behavior is non-deterministic: if the async write happens to complete
before the context closes, the cache is populated. Otherwise, it silently fails.
No exception is thrown. No log is emitted.
After upgrading from 3.x to 4.x, our cache warmup batch stopped working
intermittently — with no error in sight.
Workaround
The
immediateWrites()option onRedisCacheWriterConfigurerrestoressynchronous write behavior:
This works, but:
RedisCacheWriterConfigureris not easy to discoverSuggestion
asynchronous by default when using a reactive driver, and document
immediateWrites()as the opt-in for the previous behavior.