{"record":{"id":"da4b04026bcf9192","repo":"spring-projects/spring-ai","slug":"unsupported-exchange-type-exchangeclassname-null-da4b04","errorCode":null,"errorMessage":"Unsupported exchange type: {exchangeClassName|null} for method: {methodName} in {className}","messagePattern":"Unsupported exchange type: (.+?) for 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":93,"sourceCode":"\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}\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 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":75,"sourceCodeEnd":111,"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#L75-L111","documentation":"The final fall-through in assignExchangeType: the method's declared exchange parameter type matches none of the supported types' dispatch paths, and the runtime exchange object is not an instance the library recognizes. It throws IllegalArgumentException naming the actual exchange class (or null) plus the method and class.","triggerScenarios":"A sync resource method's parameter type is neither McpTransportContext-assignable nor McpSyncServerExchange-assignable (e.g., a custom exchange type or unrelated class), so validation passed by inheritance accident but assignExchangeType has no branch to handle the runtime value.","commonSituations":"Custom wrapper types around the exchange, or a signature edited to accept an unsupported type after an upgrade changed the supported parameter set.","solutions":["Change the parameter to one of the supported types: McpSyncServerExchange or McpTransportContext.","Check the library version's documented supported parameter types for resource methods and align the signature.","Remove the exchange parameter if the handler does not need transport/exchange metadata."],"exampleFix":"// before\n@McpResource(uri = \"cfg://{key}\")\npublic String get(MyCustomExchange ex, String key) { ... }\n\n// after\n@McpResource(uri = \"cfg://{key}\")\npublic String get(McpSyncServerExchange ex, String key) { ... }","handlingStrategy":"type-guard","validationCode":"void checkExchangeParamType(Class<?> paramType) {\n    boolean ok = McpSyncServerExchange.class.isAssignableFrom(paramType)\n        || McpTransportContext.class.isAssignableFrom(paramType)\n        || paramType == Object.class /* exchange as plain arg */;\n    if (!ok) throw new IllegalArgumentException(\"Unsupported exchange parameter: \" + paramType);\n}","typeGuard":"static boolean isSupportedExchangeParam(Class<?> p) {\n    return McpSyncServerExchange.class.isAssignableFrom(p) || McpTransportContext.class.isAssignableFrom(p);\n}","tryCatchPattern":"try {\n    registerResource(method, callback);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Unsupported exchange type\")) {\n        log.error(\"Use McpSyncServerExchange or McpTransportContext for the exchange parameter\", e);\n    } throw e;\n}","preventionTips":["Stick to documented parameter types (McpSyncServerExchange, McpTransportContext) for resource methods.","Re-check signatures after upgrading spring-ai MCP versions, as supported parameter sets evolve.","Avoid custom exchange wrapper classes in handler signatures."],"tags":["mcp","exchange-type","unsupported-parameter","illegal-argument"],"backgroundTag":"unsupported-operation","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"}