spring-projects/spring-ai · error · IllegalArgumentException
Method must return Mono<ElicitResult> or Mono<StructuredElic
Error message
Method must return Mono<ElicitResult> or Mono<StructuredElicitResult>:
What it means
Thrown by AsyncMcpElicitationMethodCallback.validateReturnType when an async @McpElicitation-annotated method's return type is not a Mono. Async elicitation handlers must return Mono<ElicitResult> (or Mono<StructuredElicitResult>) so the framework can subscribe reactively.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/elicitation/AsyncMcpElicitationMethodCallback.java:119
"Method must return Mono<ElicitResult> or Mono<StructuredElicitResult>: " + this.method.getName()));
}
catch (Exception e) {
return Mono.error(new McpElicitationMethodException(
"Error invoking elicitation method: " + this.method.getName(), e));
}
}
/**
* Validates that the method return type is compatible with the elicitation callback.
* @param method The method to validate
* @throws IllegalArgumentException if the return type is not compatible
*/
@Override
protected void validateReturnType(Method method) {
Class<?> returnType = method.getReturnType();
if (!Mono.class.isAssignableFrom(returnType)) {
throw new IllegalArgumentException(
"Method must return Mono<ElicitResult> or Mono<StructuredElicitResult>: " + method.getName()
+ " in " + method.getDeclaringClass().getName() + " returns " + returnType.getName());
}
}
/**
* Checks if a parameter type is compatible with the exchange type.
* @param paramType The parameter type to check
* @return true if the parameter type is compatible with the exchange type, false
* otherwise
*/
@Override
protected boolean isExchangeType(Class<?> paramType) {
// No exchange type for elicitation methods
return false;
}
/**View on GitHub (pinned to 98a7beda4f)
Solutions
- Change the return type to Mono<ElicitResult>
- Wrap a blocking computation with Mono.fromCallable(() -> computeResult(req))
- If the method is inherently synchronous, register it via the sync specification/callback instead of the async one
- For structured payloads return Mono<StructuredElicitResult> instead
Example fix
// before
@McpElicitation
public ElicitResult handle(ElicitRequest request) { return compute(request); }
// after
@McpElicitation
public Mono<ElicitResult> handle(ElicitRequest request) {
return Mono.fromCallable(() -> compute(request));
}
Defensive patterns
Strategy: validation
Validate before calling
if (!Mono.class.isAssignableFrom(handlerMethod.getReturnType())) {
throw new IllegalArgumentException("Async elicitation handler must return Mono<ElicitResult>: " + handlerMethod);
} Type guard
boolean isAsyncElicitationHandler(Method m) {
return Mono.class.isAssignableFrom(m.getReturnType());
} Prevention
- Return Mono<ElicitResult> (or Mono<StructuredElicitResult>) from async handlers
- Use Mono.fromCallable to adapt blocking code
- Use sync registration for plain blocking handlers instead of forcing Mono
When it happens
Trigger: Registering a handler on the async callback path declared as public ElicitResult handle(ElicitRequest req) (sync return) or returning Flux/CompletableFuture instead of Mono.
Common situations: Using @McpElicitation in a reactive (WebFlux / async MCP client) setup but writing the handler in blocking style, or migrating a sync handler to async without changing the return type.
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
- Method must have void or Mono<Void> return type:
- Method must return ElicitResult or StructuredElicitResult:
- Method must return ElicitResult:
- Expected Mono<Void> but got Mono<
- Expected Mono<Void> but got Mono<
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/4c3b2240abe2988a.
Report an issue: GitHub.