spring-projects/spring-ai · error · IllegalArgumentException
Unsupported exchange type: {exchangeClassName|null} for meth
Error message
Unsupported exchange type: {exchangeClassName|null} for method: {methodName} in {className} What it means
Thrown by assignExchangeType when an MCP annotated resource method is declared sync but the framework injects an exchange object of an unsupported (async) class, or the exchange is null and no matching parameter binding applies. The library distinguishes Sync vs Async callbacks and only allows the exchange type matching the callback flavor.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/SyncStatelessMcpResourceMethodCallback.java:88
@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 extracts URI variable values from the request URI, builds the arguments
* for the method call, invokes the method, and converts the result to a
* ReadResourceResult.
* @param context The transport context, may be null if the method doesn't require it
* @param request The resource request, must not be null
* @return The resource result
* @throws McpError if there is an error invoking the resource method
* @throws IllegalArgumentException if the request is null or if URI variable
* extraction fails
*/
@OverrideView on GitHub (pinned to 98a7beda4f)
Solutions
- Change the method's exchange parameter to the sync exchange type matching the callback (or remove the parameter if not needed)
- Register the method/callback on the async server builder if the method uses an async exchange type
- Check that the exchange is non-null and of the exact class the sync callback supports (inspect the class name in the message)
- Align library versions so the supported exchange classes match those your code imports
Example fix
// before
@McpResource(uri = "file://{path}")
public String read(McpAsyncServerExchange exchange, String path) { ... }
// after
@McpResource(uri = "file://{path}")
public String read(McpSyncServerExchange exchange, String path) { ... } Defensive patterns
Strategy: validation
Validate before calling
boolean ok = java.util.stream.Stream.of(method.getParameterTypes())
.allMatch(t -> !McpAsyncServerExchange.class.isAssignableFrom(t));
if (!ok) throw new IllegalStateException("Sync resource method must not take an async exchange"); Type guard
boolean isSyncExchange(Object ex) {
return ex instanceof McpSyncServerExchange;
} Try / catch
try {
return callback.apply(context, request);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Unsupported exchange type")) {
// fall back to async callback or re-register method with sync exchange
}
throw e;
} Prevention
- Keep exchange parameter types consistent with sync vs async server builders
- Review method signatures at registration; the failure occurs at startup, not per request
- Write a startup smoke test that constructs all callbacks
When it happens
Trigger: Registering a resource method whose exchange parameter is an async type (e.g. McpAsyncServerExchange or a reactive exchange, or an unexpected type/null) on a sync stateless resource callback (SyncStatelessMcpResourceMethodCallback).
Common situations: Mixing sync and async MCP APIs: annotating a method with an async exchange parameter but registering the bean/handler via the sync server builder; passing null exchange in tests; version drift where a new exchange class is not in the supported list.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- Unsupported Async exchange type: ${exchangeType} for Sync me
- Unsupported exchange type: ${exchangeType} for method: ${met
- Method must have exactly 1 parameter (List<McpSchema.Resourc
- Parameter must be of type List<McpSchema.Resource>:
- Bean must not be null
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/fb259cadb9f7d2bb.
Report an issue: GitHub.