spring-projects/spring-ai · error · IllegalArgumentException
Unsupported Async exchange type: ${exchangeType} for Async m
Error message
Unsupported Async exchange type: ${exchangeType} for Async method: ${method} in ${declaringClass} What it means
Thrown by AsyncMcpResourceMethodCallback.assignExchangeType when the annotated async method expects a McpTransportContext parameter but the runtime exchange object is a McpSyncServerExchange. An async callback cannot be driven by a sync exchange, so it cannot supply a matching transport context and fails fast.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AsyncMcpResourceMethodCallback.java:78
@Override
protected void validateParamType(Class<?> paramType) {
if (McpSyncServerExchange.class.isAssignableFrom(paramType)) {
throw new IllegalArgumentException("Async prompt method must not declare parameter of type: "
+ paramType.getName() + ". Use McpAsyncServerExchange instead." + " Method: "
+ this.method.getName() + " in " + this.method.getDeclaringClass().getName());
}
}
@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) {
throw new IllegalArgumentException("Unsupported Async exchange type: "
+ syncServerExchange.getClass().getName() + " for Async method: " + method.getName() + " in "
+ method.getDeclaringClass().getName());
}
else if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {
return asyncServerExchange.transportContext();
}
}
else if (McpAsyncServerExchange.class.isAssignableFrom(paramType)) {
if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {
return asyncServerExchange;
}
throw new IllegalArgumentException(
"Unsupported exchange type: " + (exchange != null ? exchange.getClass().getName() : "null")
+ " for Async method: " + method.getName() + " in " + method.getDeclaringClass().getName());
}
View on GitHub (pinned to 98a7beda4f)
Solutions
- Route the request through the async server/spec builder so an McpAsyncServerExchange (or McpTransportContext) is passed.
- If the method should be sync, move it to a sync callback registration matching the exchange you actually have.
- Audit the server configuration so each annotated provider is attached to exactly one server flavor consistent with its exchange parameter.
Example fix
// before (sync path driving async callback) syncServer.callResource(...); // exchange = McpSyncServerExchange // after asyncServer.callResource(...); // exchange = McpAsyncServerExchange / McpTransportContext
Defensive patterns
Strategy: try-catch
Validate before calling
if (exchange instanceof McpSyncServerExchange) {
throw new IllegalStateException("Async callback invoked from sync path");
} Type guard
static boolean isSyncExchange(Object ex) { return ex instanceof McpSyncServerExchange; } Try / catch
try {
return callback.call(exchange, request);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("Unsupported Async exchange type")) {
throw new IllegalStateException("Routed async callback through sync dispatcher", e);
}
throw e;
} Prevention
- Register each provider on exactly one server flavor
- Use McpTransportContext parameters for provider methods shared by sync and async servers
- Cover routing in integration tests so the wrong dispatcher fails in CI
When it happens
Trigger: Invoking an @McpResource method callback (declared to take McpTransportContext) that was routed through a sync server call path, so the exchange argument passed to assignExchangeType is a McpSyncServerExchange instance.
Common situations: Registering the same annotated resource provider on both a sync and an async server and calling it via the sync path; wiring the async callback into a sync spec builder by mistake.
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
- Unsupported exchange type: ${exchangeType} for Async method:
- Async prompt method must not declare parameter of type: ${pa
- Unsupported exchange type: ${exchangeType} for method: ${met
- Method must return either ReadResourceResult, List<ResourceC
- Unsupported Sync exchange type: ${exchangeType} for Sync met
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/66f1c1dee41bdb86.
Report an issue: GitHub.