spring-projects/spring-ai · warning
ASYNC Providers don't support imperative (non-reactive) retu
Error message
ASYNC Providers don't support imperative (non-reactive) return types. Skipping method <method> with non-reactive return type <returnType>
What it means
When building an MCP server with ASYNC (reactive) providers, Spring AI inspects each annotated tool/resource/prompt method's return type. Methods with imperative (non-reactive) return types such as String or a plain object cannot be adapted to the async provider model, so they are skipped and a warning is logged instead of failing. The method is silently excluded from the resulting MCP server specification.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/common/McpPredicates.java:61
public static boolean isUriTemplate(String uri) {
return URI_VARIABLE_PATTERN.matcher(uri).find();
}
public final static Predicate<Method> isReactiveReturnType = method -> Mono.class
.isAssignableFrom(method.getReturnType()) || Flux.class.isAssignableFrom(method.getReturnType())
|| Publisher.class.isAssignableFrom(method.getReturnType());
public final static Predicate<Method> isNotReactiveReturnType = method -> !Mono.class
.isAssignableFrom(method.getReturnType()) && !Flux.class.isAssignableFrom(method.getReturnType())
&& !Publisher.class.isAssignableFrom(method.getReturnType());
public static Predicate<Method> filterNonReactiveReturnTypeMethod() {
return method -> {
if (isReactiveReturnType.test(method)) {
return true;
}
if (logger.isWarnEnabled()) {
logger.warn("ASYNC Providers don't support imperative (non-reactive) return types. Skipping method "
+ method + " with non-reactive return type " + method.getReturnType());
}
return false;
};
}
public static Predicate<Method> filterReactiveReturnTypeMethod() {
return method -> {
if (isNotReactiveReturnType.test(method)) {
return true;
}
if (logger.isWarnEnabled()) {
logger.warn("SYNC Providers don't support reactive return types. Skipping method " + method
+ " with reactive return type " + method.getReturnType());
}
return false;
};
}View on GitHub (pinned to 98a7beda4f)
Solutions
- Change the annotated method's return type to a reactive type (Mono<T> or Flux<T>) and wrap the existing logic, e.g. Mono.just(...)
- Register the bean with a SYNC server/provider instead of ASYNC if reactive signatures are not desired
- Split the annotated service so reactive-only methods live in the class registered with the async provider
Example fix
// before
@McpTool(description = "Greeting")
public String greet(String name) { return "Hello " + name; }
// after
@McpTool(description = "Greeting")
public Mono<String> greet(String name) { return Mono.just("Hello " + name); } Defensive patterns
Strategy: validation
Validate before calling
for (Method m : toolService.getClass().getDeclaredMethods()) {
if (m.isAnnotationPresent(McpTool.class) && !reactiveType.test(m.getReturnType())) {
throw new IllegalStateException(m + " must return Mono/Flux for ASYNC providers");
}
}
// reactiveType: Class<?> r -> r == Mono.class || r == Flux.class Type guard
static boolean isReactiveReturn(Method m) {
Class<?> r = m.getReturnType();
return Mono.class.isAssignableFrom(r) || Flux.class.isAssignableFrom(r);
} Prevention
- Use consistent return types (Mono/Flux) in services registered with async servers
- Keep separate service classes for sync and async registrations
- Watch startup logs for 'Skipping method ... non-reactive return type' warnings
When it happens
Trigger: Registering a @McpTool/@McpResource annotated bean with an ASYNC MCP server (McpServer.async(...) or async provider builder) where one or more annotated methods return plain types (String, POJOs) instead of Mono/Flux.
Common situations: Sharing the same annotated service class between a sync and an async server; migrating code from the sync starter to the async starter without changing method signatures; developers forgetting that async providers require reactive return types.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- SYNC Providers don't support reactive return types. Skipping
- Mono return type must be Mono<Void>: {method.getName()} in {
- Async complete methods should use McpAsyncRequestContext ins
- No async complete methods found in the provided complete obj
- Expected Mono<Void> but got Mono<
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/27f0b3857ba399e8.
Report an issue: GitHub.