In the CacheAspectSupport.findInCaches method CompletableFuture<?> cachedFuture = cache.retrieve(key); spring cache result is null, spring redis cache result is CompletableFuture.completedFuture(null) causing result return error

@Nullable
public Object findInCaches(CacheOperationContext context, Cache cache, Object key) {
    ReactiveAdapter adapter = this.registry.getAdapter(context.getMethod().getReturnType());
    if (adapter != null) {
        CompletableFuture<?> cachedFuture = cache.retrieve(key);
        if (cachedFuture == null) {
            return null;
        }
        if (adapter.isMultiValue()) {
            return adapter.fromPublisher(Flux.from(Mono.fromFuture(cachedFuture))
                    .flatMap(v -> (v instanceof Iterable<?> iv ? Flux.fromIterable(iv) : Flux.just(v))));
        }
        else {
            return adapter.fromPublisher(Mono.fromFuture(cachedFuture));
        }
    }
    return NOT_HANDLED;
}

Comment From: mp911de

Thanks for reaching out. I think you've spotted a shortcoming of the design. The current approach expects a null value for a cache miss and a CompletableFuture for any cache hit.

Arguably this approach can work for in-memory caches. Any cache that requires I/O to determine whether there is a cache entry (such as Redis) would be required to await Future completion making the async cache API a blocking code path.

It makes sense to revisit this arrangement with the team.

Comment From: jhoeller

A side note: Reactive caching can also work with the @Cacheable(sync=true) mode of operation which delegates to the value loader based SPI method retrieve(key, Supplier<CompletableFuture>), restricted to a single cache and a synchronized retrieve+put step. Could you give that a try and see whether that kind of declaration works as expected with Redis?

As for the regular mode of operation, I've revised the cache interceptor implementation to be able to deal with Redis-style late-determined cache misses through an empty CompletableFuture as well. This will make it into the 6.1.1 release on Thursday, just in time for the Spring Boot 3.2 GA release. We'll make sure to test this with Redis tomorrow.

As a bonus, in order to support null cached values, such a late-determine retrieve(key) implementation can also return a CompletableFuture with a nested ValueWrapper, differentiating between null as a cache miss and null as a cached value. Spring's common processing can deal with all variants of these implementation strategies now.