quarkusio/quarkus · error · CacheException

Unhandled async return type

Error message

Unhandled async return type

What it means

asyncInvocationResultToUni converts an intercepted method's result into a Uni for caching. It supports Uni and CompletionStage return types; if it is invoked with any other ReturnType (i.e. NonAsync slipped through), it throws CacheException wrapping an IllegalStateException. This normally indicates an internal dispatch bug rather than user error.

Source

Thrown at extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInterceptor.java:245

        }
        if (CompletionStage.class.isAssignableFrom(returnType)) {
            return ReturnType.CompletionStage;
        }
        return ReturnType.NonAsync;
    }

    protected Uni<?> asyncInvocationResultToUni(Object invocationResult, ReturnType returnType) {
        if (returnType == ReturnType.Uni) {
            return (Uni<?>) invocationResult;
        } else if (returnType == ReturnType.CompletionStage) {
            return Uni.createFrom().completionStage(new Supplier<>() {
                @Override
                public CompletionStage<?> get() {
                    return (CompletionStage<?>) invocationResult;
                }
            });
        } else {
            throw new CacheException(new IllegalStateException(UNHANDLED_ASYNC_RETURN_TYPE_MSG));
        }
    }

    protected Object createAsyncResult(Uni<Object> cacheValue, ReturnType returnType) {
        if (returnType == ReturnType.Uni) {
            return cacheValue;
        }
        if (returnType == ReturnType.CompletionStage) {
            return cacheValue.subscribeAsCompletionStage();
        }
        throw new CacheException(new IllegalStateException(UNHANDLED_ASYNC_RETURN_TYPE_MSG));
    }

    protected enum ReturnType {
        NonAsync,
        Uni,
        CompletionStage
    }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Verify the cached method returns exactly Uni or CompletionStage (or a plain, non-async type) and nothing exotic.
  2. If you subclass CacheInterceptor, ensure determineReturnType and asyncInvocationResultToUni stay consistent.
  3. If hit without custom code, report it as a Quarkus bug with the method signature and version.

Example fix

// before
@CacheResult(cacheName = "x")
public SomeReactiveType get(String id) { ... } // unsupported async type

// after
@CacheResult(cacheName = "x")
public Uni<SomeType> get(String id) { ... }
Defensive patterns

Strategy: validation

Validate before calling

Class<?> rt = cachedMethod.getReturnType();
if (!io.smallrye.mutiny.Uni.class.isAssignableFrom(rt)
        && !java.util.concurrent.CompletionStage.class.isAssignableFrom(rt)
        && isAsyncDispatch(rt)) {
    throw new IllegalStateException("Return Uni or CompletionStage for async caching");
}

Prevention

When it happens

Trigger: A cached method whose declared return type was classified as NonAsync by determineReturnType but still routed through the async conversion path — practically only reachable via subclassing CacheInterceptor or inconsistent classification (e.g. exotic return types like Uni subtypes handled inconsistently across custom interceptor subclasses).

Common situations: Custom interceptor subclasses overriding determineReturnType or the invocation flow; framework-level regressions after upgrading Quarkus; Kotlin suspend/CompletableFuture variants misclassified.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/72a4cfc445c7a22f. Report an issue: GitHub.