{"record":{"id":"4fdcde2ebd4a6a90","repo":"spring-projects/spring-ai","slug":"unsupported-exchange-type-exchangetype-for-met-4fdcde","errorCode":null,"errorMessage":"Unsupported exchange type: ${exchangeType} for method: ${method} in ${declaringClass}","messagePattern":"Unsupported exchange type: (.+?) for method: (.+?) in (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AsyncStatelessMcpResourceMethodCallback.java","lineNumber":91,"sourceCode":"\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\tthrow new IllegalArgumentException(\"Unsupported Sync exchange type: \"\n\t\t\t\t\t\t+ syncServerExchange.getClass().getName() + \" for Sync 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\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 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 context The transport context, 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\n\t * @throws McpError if there is an error invoking the resource method\n\t * @throws IllegalArgumentException if the request is null or if URI variable\n\t * extraction fails\n\t */\n\t@Override","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AsyncStatelessMcpResourceMethodCallback.java#L73-L109","documentation":"The fall-through throw in AsyncStatelessMcpResourceMethodCallback.assignExchangeType: the exchange argument is neither a McpTransportContext, McpSyncServerExchange, nor McpAsyncServerExchange (it may also be null), so the stateless callback cannot bind it to the method's McpTransportContext parameter.","triggerScenarios":"Manually invoking the stateless resource callback with a null exchange or an unrelated object type; custom transport code that builds its own exchange object.","commonSituations":"Unit tests calling the callback directly without constructing a McpTransportContext; custom HTTP layer passing a request object instead of a transport context.","solutions":["Pass a valid McpTransportContext when calling the callback (e.g., McpTransportContext.empty() in tests).","Fix the dispatcher so it always derives the transport context from the incoming HTTP request.","Handle null exchanges explicitly before invoking the annotated method."],"exampleFix":"// before\ncallback.call(null, request);\n\n// after\ncallback.call(McpTransportContext.empty(), request);","handlingStrategy":"type-guard","validationCode":"if (exchange == null) throw new IllegalArgumentException(\"McpTransportContext is required by stateless resource callbacks\");","typeGuard":"static boolean hasTransportContext(Object ex) { return ex instanceof McpTransportContext; }","tryCatchPattern":"if (!(exchange instanceof McpTransportContext ctx)) {\n    exchange = McpTransportContext.empty(); // or fail fast\n}\nreturn callback.call(ctx, request);","preventionTips":["Always build a McpTransportContext from the HTTP request before dispatch","Use McpTransportContext.empty() in unit tests","Never pass arbitrary objects or null as the exchange argument"],"tags":["mcp","java","stateless","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"}