{"record":{"id":"aa530329859646bf","repo":"spring-projects/spring-ai","slug":"unsupported-exchange-type-exchangetype-for-asy","errorCode":null,"errorMessage":"Unsupported exchange type: ${exchangeType} for Async method: ${method} in ${declaringClass}","messagePattern":"Unsupported exchange type: (.+?) for Async method: (.+?) in (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AsyncMcpResourceMethodCallback.java","lineNumber":92,"sourceCode":"\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\tthrow new IllegalArgumentException(\"Unsupported Async exchange type: \"\n\t\t\t\t\t\t+ syncServerExchange.getClass().getName() + \" for Async method: \" + method.getName() + \" in \"\n\t\t\t\t\t\t+ method.getDeclaringClass().getName());\n\n\t\t\t}\n\t\t\telse if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {\n\t\t\t\treturn asyncServerExchange.transportContext();\n\t\t\t}\n\t\t}\n\t\telse if (McpAsyncServerExchange.class.isAssignableFrom(paramType)) {\n\t\t\tif (exchange instanceof McpAsyncServerExchange asyncServerExchange) {\n\t\t\t\treturn asyncServerExchange;\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 Async 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}\n\n\t/**\n\t * Apply the callback to the given exchange and request.\n\t * <p>\n\t * This method extracts URI variable values from the request URI, builds the arguments\n\t * for the method call, invokes the method, and converts the result to a\n\t * ReadResourceResult.\n\t * @param exchange The server exchange, may be null if the method doesn't require it\n\t * @param request The resource request, must not be null\n\t * @return A Mono that emits the resource result","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AsyncMcpResourceMethodCallback.java#L74-L110","documentation":"Thrown by AsyncMcpResourceMethodCallback.assignExchangeType when the async method declares a McpAsyncServerExchange parameter but the exchange supplied at call time is not one (it is some other type, or null). The async callback can only bind an McpAsyncServerExchange to that parameter, so it throws.","triggerScenarios":"Calling the async resource method callback with a null exchange, or an exchange that is neither McpAsyncServerExchange nor convertible (e.g., only a McpSyncServerExchange or unrelated object) when the method signature requires McpAsyncServerExchange.","commonSituations":"Manually invoking the callback from tests or custom transport glue with the wrong exchange object; a custom server integration that forgets to build an async exchange before dispatch.","solutions":["Pass an McpAsyncServerExchange when invoking the callback (build it from the async server session).","If you only have a transport context, change the method parameter to McpTransportContext.","Fix null exchanges by ensuring the dispatcher always resolves a session before calling the annotated method."],"exampleFix":"// before\ncallback.call(syncExchange, request); // wrong exchange type\n\n// after\nMcpAsyncServerExchange ex = new McpAsyncServerExchange(session, transportContext);\ncallback.call(ex, request);","handlingStrategy":"type-guard","validationCode":"if (exchange == null) throw new IllegalArgumentException(\"exchange required for @McpAsyncServerExchange methods\");","typeGuard":"static boolean canInvokeAsync(Object exchange) { return exchange instanceof McpAsyncServerExchange; }","tryCatchPattern":"if (!canInvokeAsync(exchange)) {\n    throw new IllegalStateException(\"Callback requires McpAsyncServerExchange, got: \" + exchange);\n}\nreturn callback.call(exchange, request);","preventionTips":["Always construct an async exchange from the session before dispatching","Never pass null exchange into annotated method callbacks","In tests, build a real McpAsyncServerExchange rather than a mock wrapper"],"tags":["mcp","java","async","exchange-mismatch"],"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"}