spring-projects/spring-ai · error · java.lang.IllegalArgumentException
Unsupported Async exchange type: {syncServerExchange.getClas
Error message
Unsupported Async exchange type: {syncServerExchange.getClass().getName()} for Async method: {method.getName()} in {method.getDeclaringClass().getName()} What it means
In AsyncMcpPromptMethodCallback.assignExchangeType, when a method parameter is a McpTransportContext but the runtime exchange passed by the server is a McpSyncServerExchange, the callback cannot supply a transport context from a sync exchange and throws this IllegalArgumentException at invocation time. It indicates a sync/async mismatch: an async-declared method is being driven by a sync server exchange.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/AsyncMcpPromptMethodCallback.java:69
@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
- Register the method with the correct callback variant: async methods only on McpAsyncServer / async annotation processor.
- Change the parameter to McpAsyncServerExchange if the surrounding server is sync-driven, or vice versa.
- Audit duplicated registrations so the same @McpPrompt method is not wired to both sync and async servers.
Example fix
// before (registered on sync server)
@McpPrompt(name = "report")
public Mono<String> report(McpTransportContext ctx) { ... }
// after (register with async server, or change parameter)
@McpPrompt(name = "report")
public Mono<String> report(McpAsyncServerExchange exchange) { ... } Defensive patterns
Strategy: validation
Validate before calling
// before registering:
if (server instanceof McpSyncServer && usesTransportContextOrAsyncParams(method)) {
throw new IllegalStateException("Method must be registered on the async server: " + method);
} Type guard
boolean isSyncExchange(Object exchange) { return exchange instanceof McpSyncServerExchange; } Try / catch
try {
return callback.apply(exchange, request);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("Unsupported Async exchange type")) {
throw new IllegalStateException("Method registered with wrong server variant (sync vs async)", e);
}
throw e;
} Prevention
- Register each prompt method with exactly one server variant (sync OR async).
- When migrating sync->async, change both return types and exchange parameters together.
- Add integration tests exercising every prompt through the actual server instance.
When it happens
Trigger: An async prompt method declares `McpTransportContext ctx` but the callback is invoked with a McpSyncServerExchange instance (e.g. the method was registered on both a sync and async server, or the sync callback dispatched it).
Common situations: Registering the same prompt bean with both McpSyncServer and McpAsyncServer; mixing sync/async annotation processors over the same bean; refactoring a sync handler to async parameter types without changing server wiring.
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
- Async prompt method must not declare parameter of type: {par
- Unsupported exchange type: {exchange != null ? exchange.getC
- Unsupported exchange type: {exchange != null ? exchange.getC
- Unsupported Sync exchange type: {syncServerExchange.getClass
- Unsupported Async exchange type: {asyncServerExchange.getCla
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/e248d9772310090a.
Report an issue: GitHub.