{"record":{"id":"55a313260d1b2a5a","repo":"spring-projects/spring-ai","slug":"unsupported-async-exchange-type-exchangeclassnam","errorCode":null,"errorMessage":"Unsupported Async exchange type: {exchangeClassName} for Sync method: {methodName} in {className}","messagePattern":"Unsupported Async exchange type: (.+?) for Sync method: (.+?) in (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/SyncMcpResourceMethodCallback.java","lineNumber":78,"sourceCode":"\t\tif (McpAsyncServerExchange.class.isAssignableFrom(paramType)) {\n\t\t\tthrow new IllegalArgumentException(\"Sync prompt method must not declare parameter of type: \"\n\t\t\t\t\t+ paramType.getName() + \". Use McpSyncServerExchange instead.\" + \" Method: \" + this.method.getName()\n\t\t\t\t\t+ \" in \" + this.method.getDeclaringClass().getName());\n\t\t}\n\t}\n\n\t@Override\n\tprotected Object assignExchangeType(Class<?> paramType, Object exchange) {\n\n\t\tif (McpTransportContext.class.isAssignableFrom(paramType)) {\n\t\t\tif (exchange instanceof McpTransportContext transportContext) {\n\t\t\t\treturn transportContext;\n\t\t\t}\n\t\t\telse if (exchange instanceof McpSyncServerExchange syncServerExchange) {\n\t\t\t\treturn syncServerExchange.transportContext();\n\t\t\t}\n\t\t\telse if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {\n\t\t\t\tthrow new IllegalArgumentException(\"Unsupported Async exchange type: \"\n\t\t\t\t\t\t+ asyncServerExchange.getClass().getName() + \" for Sync method: \" + method.getName() + \" in \"\n\t\t\t\t\t\t+ method.getDeclaringClass().getName());\n\t\t\t}\n\t\t}\n\t\telse if (McpSyncServerExchange.class.isAssignableFrom(paramType)) {\n\t\t\tif (exchange instanceof McpSyncServerExchange syncServerExchange) {\n\t\t\t\treturn syncServerExchange;\n\t\t\t}\n\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Unsupported exchange type: \" + (exchange != null ? exchange.getClass().getName() : \"null\")\n\t\t\t\t\t\t\t+ \" for Sync method: \" + method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t}\n\n\t\tthrow new IllegalArgumentException(\n\t\t\t\t\"Unsupported exchange type: \" + (exchange != null ? exchange.getClass().getName() : \"null\")\n\t\t\t\t\t\t+ \" for method: \" + method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t}","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/SyncMcpResourceMethodCallback.java#L60-L96","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (async server using a sync-declared handler)\nMcpAsyncServer asyncServer = ...;\nasyncServer.addResource(..., (exchange, req) -> syncCallback.apply(exchange, req));\n\n// after\nMcpSyncServer syncServer = ...;\nsyncServer.addResource(..., syncCallback);","handlingStrategy":"type-guard","validationCode":"Object safeExchangeForSync(Object exchange) {\n    if (exchange instanceof McpAsyncServerExchange)\n        throw new IllegalArgumentException(\"Async exchange passed to sync resource callback\");\n    return exchange;\n}","typeGuard":"static boolean isSyncCompatibleExchange(Object exchange) {\n    return !(exchange instanceof McpAsyncServerExchange);\n}","tryCatchPattern":"try {\n    return callback.apply(exchange, request);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Unsupported Async exchange type\")) {\n        log.error(\"Registered sync handler is being driven by an async exchange; check server wiring\", e);\n        throw new IllegalStateException(\"Handler/server wiring mismatch\", e);\n    } throw e;\n}","preventionTips":["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."],"tags":["mcp","sync-async-mismatch","exchange-type","illegal-argument"],"backgroundTag":"type-mismatch","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}