spring-projects/spring-ai · error · IllegalArgumentException
Method must return CreateMessageResult: {methodName} in {cla
Error message
Method must return CreateMessageResult: {methodName} in {className} returns {returnTypeName} What it means
SyncMcpSamplingMethodCallback.validateReturnType throws IllegalArgumentException when a method registered as a sync sampling callback does not return a CreateMessageResult (or subtype). Unlike the async variant, Mono is not accepted here; the sync path invokes the method and casts the raw result to CreateMessageResult.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/sampling/SyncMcpSamplingMethodCallback.java:86
// Return the result
return (CreateMessageResult) result;
}
catch (Exception e) {
throw new McpSamplingMethodException("Error invoking sampling method: " + this.method.getName(), e);
}
}
/**
* Validates that the method return type is compatible with the sampling 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 (!CreateMessageResult.class.isAssignableFrom(returnType)) {
throw new IllegalArgumentException("Method must return CreateMessageResult: " + 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 sampling methods
return false;
}
/**
* Create a new builder.View on GitHub (pinned to 98a7beda4f)
Solutions
- Change the method return type to CreateMessageResult
- Wrap the produced value: return new CreateMessageResult(...) inside the handler
- If the handler is truly reactive, register it under AsyncSamplingSpecification instead
Example fix
// before
Mono<CreateMessageResult> sample(CreateMessageRequest req) { ... }
// after
CreateMessageResult sample(CreateMessageRequest req) {
return new CreateMessageResult(buildMessage(req));
} Defensive patterns
Strategy: validation
Validate before calling
if (!CreateMessageResult.class.isAssignableFrom(method.getReturnType())) {
throw new IllegalArgumentException(method + " must return CreateMessageResult for sync sampling");
} Try / catch
try { builder.build(); } catch (IllegalArgumentException e) { log.error("Sync sampling return type invalid: {}", e.getMessage()); } Prevention
- Give sync sampling methods plain CreateMessageResult signatures
- Route Mono-returning methods to AsyncSamplingSpecification instead
- Add reflection-based signature checks in application tests
When it happens
Trigger: Registering a sampling method returning String, void, Mono<CreateMessageResult>, or any other type on the sync callback path, then constructing the callback.
Common situations: Using a reactive-style handler method in a SyncSamplingSpecification; forgetting to adapt the return type when migrating from async to sync specs; returning a DTO instead of CreateMessageResult.
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
- Synchronous progress methods must return void: {method.getNa
- Method must return Mono<CreateMessageResult> or CreateMessag
- Request must not be null
- Sampling not supported by the client:
- Expected Mono<Void> but got Mono<
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/d2275d6366168059.
Report an issue: GitHub.