spring-projects/spring-ai · error · IllegalStateException
Method must return ElicitResult or StructuredElicitResult:
Error message
Method must return ElicitResult or StructuredElicitResult:
What it means
Thrown as IllegalStateException by SyncMcpElicitationMethodCallback.apply when the sync handler method's declared return type is neither ElicitResult nor StructuredElicitResult. Unlike the async variant (which validates return type upfront), the sync callback checks the actual returned object at invocation time; simple types and custom objects are a TODO.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/elicitation/SyncMcpElicitationMethodCallback.java:92
var content = structuredElicitResult.structuredContent() != null
? jsonHelper.convertToMap(structuredElicitResult.structuredContent()) : null;
return ElicitResult.builder(structuredElicitResult.action())
.content(content)
.meta(structuredElicitResult.meta())
.build();
}
else if (this.method.getReturnType().isAssignableFrom(ElicitResult.class)) {
// If the method returns ElicitResult, return it directly
return (ElicitResult) result;
}
else {
// TODO add support for methods returning simple types or Objects of
// elicitation schema type.
throw new IllegalStateException("Method must return ElicitResult or StructuredElicitResult: "
+ this.method.getName() + " in " + this.method.getDeclaringClass().getName() + " returns "
+ this.method.getReturnType().getName());
}
}
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();
View on GitHub (pinned to 98a7beda4f)
Solutions
- Change the handler to return ElicitResult, e.g. ElicitResult.builder()...build() with the accepted action and content map
- Return StructuredElicitResult for typed/structured responses
- Wrap simple values manually: new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("value", myValue))
- Note validateReturnType typically catches this earlier; if you see it at runtime, the method was registered without return-type validation
Example fix
// before
@McpElicitation
public String confirm(ElicitRequest request) { return "yes"; }
// after
@McpElicitation
public ElicitResult confirm(ElicitRequest request) {
return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("answer", "yes"));
}
Defensive patterns
Strategy: validation
Validate before calling
Class<?> rt = handlerMethod.getReturnType();
if (!ElicitResult.class.isAssignableFrom(rt) && !StructuredElicitResult.class.isAssignableFrom(rt)) {
throw new IllegalArgumentException("Sync handler must return ElicitResult or StructuredElicitResult: " + handlerMethod);
} Type guard
boolean isValidSyncReturnType(Method m) {
return ElicitResult.class.isAssignableFrom(m.getReturnType())
|| StructuredElicitResult.class.isAssignableFrom(m.getReturnType());
} Try / catch
try {
return callback.apply(request);
} catch (IllegalStateException e) {
log.error("Handler returns unsupported type: {}", e.getMessage());
throw e;
} Prevention
- Always return ElicitResult or StructuredElicitResult from sync handlers
- Build ElicitResult explicitly with an Action (ACCEPT/DECLINE/CANCEL) and content map
- Rely on validateReturnType at registration time so this surfaces before runtime
When it happens
Trigger: A sync @McpElicitation handler returning String, Map, custom POJO, void, or any other type: public String confirm(ElicitRequest req). The method must return ElicitResult or StructuredElicitResult.
Common situations: Developers expect the framework to wrap returned simple values into an ElicitResult automatically (that support is not implemented), or they return the elicited value directly instead of building an ElicitResult.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Currently only methods with a single ElicitRequest parameter
- Method must return Mono<ElicitResult> or Mono<StructuredElic
- Method must return ElicitResult:
- Method must have void or Mono<Void> return type:
- Method must have void return type: " + method.getName() + "
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/c328a68959893ba1.
Report an issue: GitHub.