spring-projects/spring-ai · error · IllegalArgumentException
Method must return ElicitResult:
Error message
Method must return ElicitResult:
What it means
validateReturnType() in SyncMcpElicitationMethodCallback enforces that a method registered as an elicitation callback returns ElicitResult or StructuredElicitResult (or a subtype). Any other return type is rejected with IllegalArgumentException at registration time. The MCP elicitation protocol requires the callback to produce an ElicitResult to send back to the client.
Solutions
- Change the method return type to ElicitResult (or StructuredElicitResult)
- Wrap the current return value in an ElicitResult with the appropriate action
- If the method is not meant to be an elicitation handler, remove the @McpElicitation annotation
Example fix
// before
@McpElicitation(clients = "client1")
public void handleForm(ElicitRequest request) { ... }
// after
@McpElicitation(clients = "client1")
public ElicitResult handleForm(ElicitRequest request) {
return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of());
} Defensive patterns
Strategy: validation
Validate before calling
Method m = handler.getClass().getDeclaredMethod("askUser", ElicitRequest.class);
if (!ElicitResult.class.isAssignableFrom(m.getReturnType())
&& !StructuredElicitResult.class.isAssignableFrom(m.getReturnType())) {
throw new IllegalStateException("return type must be ElicitResult/StructuredElicitResult");
} Type guard
static boolean isValidElicitationHandler(Method m) {
return ElicitResult.class.isAssignableFrom(m.getReturnType())
|| StructuredElicitResult.class.isAssignableFrom(m.getReturnType());
} Prevention
- Declare the return type as ElicitResult at the method signature level
- Never return void or domain objects from @McpElicitation methods
- Write a unit test asserting every annotated method passes validateReturnType
When it happens
Trigger: Annotating a method with @McpElicitation whose return type is void, String, Map, or any non-ElicitResult type; building a SyncMcpElicitationMethodCallback via its builder with such a method.
Common situations: Developer treats the annotation like a plain event handler and returns void or a domain object; refactoring changed the return type after registration code was written; copy-pasted handler from a logging/notification callback.
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 return ElicitResult or StructuredElicitResult:
- Method must return Mono
- Bean must not be null
- clients must not be empty
- clients must not be empty
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/66b9fa9794aac3f6.
Report an issue: GitHub.
Appendix: source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/elicitation/SyncMcpElicitationMethodCallback.java:113
}
}
catch (Exception e) {
throw 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 (!ElicitResult.class.isAssignableFrom(returnType)
&& !StructuredElicitResult.class.isAssignableFrom(returnType)) {
throw new IllegalArgumentException("Method must return ElicitResult: " + 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;
}
/**
* Create a new builder.View on GitHub (pinned to 98a7beda4f)