spring-projects/spring-framework · error · IllegalArgumentException

Not a Reactive Streams Publisher: {}

Error message

Not a Reactive Streams Publisher: {}

What it means

CoroutinesUtils.asFlow() (line 34) is invoked from CglibAopProxy.processReturnType() when a Kotlin suspending function returns a Flow and the returned object needs converting to kotlinx.coroutines.flow.Flow via ReactiveFlowKt.asFlow(Publisher). That conversion requires a Reactive Streams Publisher; anything else is rejected (line 39).

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 69bf83ad71)

Solutions

  1. Ensure the proxied suspending function actually returns a Reactive Streams Publisher (e.g. a Flux/Mono) that asFlow can convert
  2. Do not let advice return null for a Flow-returning suspending function
  3. If returning null is valid, handle it before the proxy's processReturnType path (return an empty Flow/Publisher instead)

Example fix

// before
suspend fun flow(): Flow<Int> = error("...")
// advice returns null -> 'Not a Reactive Streams Publisher: null'

// after
// make the function return an actual Publisher-backed Flow,
// and have advice return an empty publisher: Flux.empty()
Defensive patterns

Strategy: type-guard

Validate before calling

// Before converting, ensure the value is a Publisher:
if (!(value instanceof Publisher)) {
  throw new IllegalStateException("Expected a Reactive Streams Publisher, got: " + value);
}

Type guard

private static boolean isPublisher(Object v) {
  return v instanceof org.reactivestreams.Publisher;
}

Prevention

When it happens

Trigger: A Kotlin suspending function (proxied by Spring AOP) whose last parameter type is kotlinx.coroutines.flow.Flow returns a value that is not a Reactive Streams Publisher (e.g. null, a plain object, a Mono handled on the wrong branch).

Common situations: A Kotlin suspend function returning a Flow whose underlying implementation returns a non-Publisher (or null) due to a bug or an unexpected return type; mismatch between the declared Flow return and what the advice/target actually returns; a custom advice that substitutes a non-Publisher value.

Related errors


AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09). Data as JSON: /api/errors/1edf79bb156a9143. Report an issue: GitHub.