{"record":{"id":"e1d1374249fc79d6","repo":"spring-projects/spring-ai","slug":"unsupported-async-exchange-type-exchangetype-f","errorCode":null,"errorMessage":"Unsupported Async exchange type: ${exchangeType} for Sync method: ${method} in ${declaringClass}","messagePattern":"Unsupported Async exchange type: (.+?) for Sync method: (.+?) in (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/SyncStatelessMcpPromptMethodCallback.java","lineNumber":75,"sourceCode":"\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Stateless Streamable-Http prompt method must not declare parameter of type: \" + paramType.getName()\n\t\t\t\t\t\t\t+ \". Use McpTransportContext instead.\" + \" Method: \" + this.method.getName() + \" in \"\n\t\t\t\t\t\t\t+ 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\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}\n\n\t/**\n\t * Apply the callback to the given context and request.\n\t * <p>\n\t * This method builds the arguments for the method call, invokes the method, and\n\t * converts the result to a GetPromptResult.\n\t * @param context The transport context, may be null if the method doesn't require it\n\t * @param request The prompt request, must not be null\n\t * @return The prompt result","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/SyncStatelessMcpPromptMethodCallback.java#L57-L93","documentation":"The sync stateless prompt callback received an McpAsyncServerExchange where it expected a sync (or context-less) exchange. Sync callbacks can only work with McpSyncServerExchange; an async exchange is incompatible with a synchronous method invocation, so assignExchangeType throws IllegalArgumentException naming the offending exchange type.","triggerScenarios":"Invoking a sync stateless prompt callback while passing an exchange object of type McpAsyncServerExchange; typically a wiring/configuration mistake where an async exchange is routed to a sync callback handler.","commonSituations":"Mixing async and sync annotated handler registrations in one application; custom code calling the callback's apply/assignExchangeType path with an exchange obtained from an async server instance.","solutions":["Register the annotated methods with a sync server (McpSyncServer) so sync callbacks receive McpSyncServerExchange.","Remove mixed async server wiring that produces McpAsyncServerExchange for the same methods.","If the method must stay sync, ensure the exchange passed down is sync or null-in-stateless mode per framework conventions."],"exampleFix":"// before (async exchange routed to sync callback)\nMcpAsyncServerExchange asyncExchange = ...;\ncallback.assignExchangeType(asyncExchange);\n\n// after (sync exchange)\nMcpSyncServerExchange syncExchange = ...;\ncallback.assignExchangeType(syncExchange);","handlingStrategy":"type-guard","validationCode":"if (exchange instanceof McpAsyncServerExchange) {\n    throw new IllegalStateException(\"Async exchange routed to sync callback - fix server wiring\");\n}","typeGuard":"static boolean isSyncExchange(Object exchange) {\n    return exchange instanceof McpSyncServerExchange || exchange == null;\n}","tryCatchPattern":"try {\n    callback.assignExchangeType(exchange);\n} catch (IllegalArgumentException e) {\n    throw new IllegalStateException(\"Sync/async exchange mismatch in MCP wiring\", e);\n}","preventionTips":["Register annotated methods with the matching server flavor (McpSyncServer for sync callbacks).","Avoid mixing async and sync server instances in the same application context.","Verify wiring with integration tests before startup."],"tags":["mcp","java","sync-async-mismatch","exchange-type"],"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"}