spring-projects/spring-ai · error · IllegalArgumentException
Unsupported Async exchange type: {exchangeClassName} for Syn
Error message
Unsupported Async exchange type: {exchangeClassName} for Sync method: {methodName} in {className} What it means
When a sync resource method declares a McpTransportContext parameter, assignExchangeType accepts a transport context or a sync exchange, but throws if the runtime exchange argument is an McpAsyncServerExchange. An async exchange cannot be adapted to a sync method invocation, so the call fails with IllegalArgumentException.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/SyncMcpResourceMethodCallback.java:78
if (McpAsyncServerExchange.class.isAssignableFrom(paramType)) {
throw new IllegalArgumentException("Sync prompt method must not declare parameter of type: "
+ paramType.getName() + ". Use McpSyncServerExchange 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) {
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());
}
}
else if (McpSyncServerExchange.class.isAssignableFrom(paramType)) {
if (exchange instanceof McpSyncServerExchange syncServerExchange) {
return syncServerExchange;
}
throw new IllegalArgumentException(
"Unsupported exchange type: " + (exchange != null ? exchange.getClass().getName() : "null")
+ " 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());
}View on GitHub (pinned to 98a7beda4f)
Solutions
- Ensure the method is registered only with a McpSyncServer so the runtime exchange is McpSyncServerExchange.
- Remove duplicate registrations of the same handler on async servers.
- If you truly need async, move the method to an async callback that accepts McpAsyncServerExchange.
Example fix
// before (async server using a sync-declared handler) McpAsyncServer asyncServer = ...; asyncServer.addResource(..., (exchange, req) -> syncCallback.apply(exchange, req)); // after McpSyncServer syncServer = ...; syncServer.addResource(..., syncCallback);
Defensive patterns
Strategy: type-guard
Validate before calling
Object safeExchangeForSync(Object exchange) {
if (exchange instanceof McpAsyncServerExchange)
throw new IllegalArgumentException("Async exchange passed to sync resource callback");
return exchange;
} Type guard
static boolean isSyncCompatibleExchange(Object exchange) {
return !(exchange instanceof McpAsyncServerExchange);
} Try / catch
try {
return callback.apply(exchange, request);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Unsupported Async exchange type")) {
log.error("Registered sync handler is being driven by an async exchange; check server wiring", e);
throw new IllegalStateException("Handler/server wiring mismatch", e);
} throw e;
} Prevention
- Register each annotated method with exactly one server type (sync or async), never both.
- In dispatch glue, assert the exchange type matches the callback type before invoking.
- Write integration tests through the actual server API rather than invoking callbacks with hand-built exchanges.
When it happens
Trigger: A sync server resource method expecting McpTransportContext (or compatible paramType) is invoked with an McpAsyncServerExchange instance as the exchange argument — e.g., the method callback is wired into an async execution path.
Common situations: Registering the same annotated method with both sync and async server providers, or manually calling the callback with an exchange obtained from an async server session.
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 Async exchange type: {exchangeClassName} for Syn
- Unsupported Async exchange type: {syncServerExchange.getClas
- Unsupported Sync exchange type: {syncServerExchange.getClass
- Unsupported Async exchange type: {asyncServerExchange.getCla
- Unsupported Async exchange type: ${exchangeType} for Sync me
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/55a313260d1b2a5a.
Report an issue: GitHub.