spring-projects/spring-ai · error · IllegalArgumentException
Unsupported exchange type: ${exchangeType} for method: ${met
Error message
Unsupported exchange type: ${exchangeType} for method: ${method} in ${declaringClass} What it means
The sync stateless prompt callback was handed an exchange object that is neither McpSyncServerExchange, McpAsyncServerExchange, nor a recognized transport context, or the exchange is null in a context where a type is required. assignExchangeType exhausts its instanceof chain and throws IllegalArgumentException naming the actual class (or "null").
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/SyncStatelessMcpPromptMethodCallback.java:81
@Override
protected Object assignExchangeType(Class<?> paramType, Object exchange) {
if (McpTransportContext.class.isAssignableFrom(paramType)) {
if (exchange instanceof McpTransportContext transportContext) {
return transportContext;
}
else if (exchange instanceof McpSyncServerExchange syncServerExchange) {
return syncServerExchange.transportContext();
}
else if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {
throw new IllegalArgumentException("Unsupported Async exchange type: "
+ asyncServerExchange.getClass().getName() + " for Sync method: " + method.getName() + " in "
+ method.getDeclaringClass().getName());
}
}
throw new IllegalArgumentException(
"Unsupported exchange type: " + (exchange != null ? exchange.getClass().getName() : "null")
+ " for method: " + method.getName() + " in " + method.getDeclaringClass().getName());
}
/**
* Apply the callback to the given context and request.
* <p>
* This method builds the arguments for the method call, invokes the method, and
* converts the result to a GetPromptResult.
* @param context The transport context, may be null if the method doesn't require it
* @param request The prompt request, must not be null
* @return The prompt result
* @throws McpError if there is an error invoking the prompt method
* @throws IllegalArgumentException if the request is null
*/
@Override
public GetPromptResult apply(McpTransportContext context, GetPromptRequest request) {
if (request == null) {View on GitHub (pinned to 98a7beda4f)
Solutions
- Ensure the caller passes McpSyncServerExchange, McpAsyncServerExchange, or McpTransportContext as expected by the sync stateless callback.
- Check that the server type (sync vs async) matches the callback type being invoked.
- If exchange may be null, verify your transport setup for the stateless Streamable-Http mode; the request must carry a recognized exchange/context.
Example fix
// before callback.assignExchangeType(someCustomExchange); // after callback.assignExchangeType(new McpSyncServerExchange(transportSession));
Defensive patterns
Strategy: type-guard
Validate before calling
if (exchange == null && callbackRequiresExchange) {
throw new IllegalArgumentException("Exchange must not be null for stateless prompt callback");
} Type guard
static boolean isRecognizedExchange(Object exchange) {
return exchange instanceof McpSyncServerExchange
|| exchange instanceof McpAsyncServerExchange
|| exchange instanceof McpTransportContext;
} Try / catch
try {
GetPromptResult result = callback.apply(context, request);
} catch (IllegalArgumentException e) {
log.error("Unsupported exchange type for stateless prompt callback: {}", e.getMessage());
} Prevention
- Only pass McpSyncServerExchange, McpAsyncServerExchange, or McpTransportContext into annotation callbacks.
- Custom transports should build framework-recognized exchange/context objects.
- Check SDK version compatibility after upgrades to ensure exchange types still match.
When it happens
Trigger: Calling assignExchangeType/apply with an exchange of an unknown class or null in stateless mode where the code path still expects a recognized exchange wrapper; e.g. a custom transport passing a raw context object.
Common situations: Custom transport implementations feeding unexpected exchange types into annotation callbacks; upgrading the MCP SDK and passing a new exchange type the annotation layer doesn't recognize; null exchange with a method that requires context extraction.
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
- Unsupported return type:
- Unsupported return type:
- Unsupported Async exchange type: ${exchangeType} for Sync me
- Unsupported exchange type: {exchangeClassName|null} for meth
- Method must have exactly 1 parameter (List<McpSchema.Resourc
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/fee4d4b8b1f7c5aa.
Report an issue: GitHub.