spring-projects/spring-framework · error · IllegalArgumentException
Not a Reactive Streams Publisher: {publisher}
Error message
Not a Reactive Streams Publisher: {publisher} What it means
Thrown by CoroutinesUtils.asFlow (package-private, invoked by both CglibAopProxy.processReturnType and JdkDynamicAopProxy.invoke) when the return value of a Kotlin suspending function expected to produce a Flow is not a Reactive Streams Publisher. Spring's Kotlin coroutine bridge converts Publisher to a Kotlin Flow via ReactiveFlowKt.asFlow; passing anything else (a Mono for a non-flow path, a plain object, null-handled elsewhere) is treated as a contract violation. This is an internal invariant for the Flow-returning suspending-function branch.
Source
Thrown at spring-aop/src/main/java/org/springframework/aop/framework/CoroutinesUtils.java:39
import kotlinx.coroutines.reactor.MonoKt;
import org.jspecify.annotations.Nullable;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
/**
* Package-visible class designed to avoid a hard dependency on Kotlin and Coroutines dependency at runtime.
*
* @author Sebastien Deleuze
* @since 6.1
*/
abstract class CoroutinesUtils {
static Object asFlow(@Nullable Object publisher) {
if (publisher instanceof Publisher<?> rsPublisher) {
return ReactiveFlowKt.asFlow(rsPublisher);
}
else {
throw new IllegalArgumentException("Not a Reactive Streams Publisher: " + publisher);
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
static @Nullable Object awaitSingleOrNull(@Nullable Object value, Object continuation) {
return MonoKt.awaitSingleOrNull(value instanceof Mono mono ? mono : Mono.justOrEmpty(value),
(Continuation<Object>) continuation);
}
}
View on GitHub (pinned to e8729d0438)
Solutions
- Ensure the value returned for a Flow-returning Kotlin suspending function is a Reactive Streams Publisher (Flux/Mono from Reactor, or a kotlinx publisher).
- If writing custom @Around advice for such methods, return the original proceed() result unchanged, or wrap your value in Flux.fromStream(...)/Mono.just(...) as appropriate.
- Align kotlinx-coroutines-reactive / kotlinx-coroutines-reactor versions with Spring's expected 6.1+ contracts.
- Avoid advising Flow-returning Kotlin functions with generic interceptors that do not understand coroutine return-type semantics.
Example fix
// before
@Around("kotlinSuspendFlowPointcut()")
public Object around(ProceedingJoinPoint pjp) {
return List.of(1, 2, 3); // not a Publisher -> throws in Flow branch
}
// after
@Around("kotlinSuspendFlowPointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
return Flux.fromIterable(List.of(1, 2, 3)); // Reactive Streams Publisher
} Defensive patterns
Strategy: type-guard
Validate before calling
Object ret = ...; // value to convert for a Flow-returning Kotlin suspending fn
if (!(ret instanceof org.reactivestreams.Publisher<?>)) {
throw new IllegalArgumentException("Expected a Reactive Streams Publisher for Flow conversion, got " + ret);
} Type guard
static boolean isPublisherForFlow(Object o) {
return o instanceof org.reactivestreams.Publisher<?>;
} Prevention
- Return only Publisher (Flux/Mono) values from advice on Flow-returning Kotlin suspending functions.
- Align kotlinx-coroutines-reactor version with Spring's expected contract.
- Avoid generic interceptors on Kotlin suspending Flow methods unless they understand coroutine return semantics.
When it happens
Trigger: A Kotlin suspending function whose last parameter type is a Flow and whose returned value is not a Publisher (e.g. a coroutine framework returns a Deferred, a raw value, or an unexpected wrapper); AOP advice that replaces the return value of such a method with a non-Publisher; version mismatch between kotlinx-coroutines/reactor bindings producing unexpected types.
Common situations: Returning the wrong reactive type from advice wrapping a Flow-returning Kotlin suspending function; mixing Reactor/Kotlin coroutine versions; a custom Around interceptor that substitutes null or a scalar for a Flow position.
Related errors
- MethodInvocation is not a Spring ProxyMethodInvocation: {}
- 'argumentNames' property of AbstractAspectJAdvice contains a
- Only afterReturning advice can be used to bind a return valu
- Returning name '{}' is neither a valid argument name nor the
- Only afterThrowing advice can be used to bind a thrown excep
AI-assisted analysis of spring-projects/spring-framework@e8729d0438 (2026-08-04).
Data as JSON: /data/errors/9418fb359e7a41c7.json.
Report an issue: GitHub.