{"record":{"id":"5b637ecf6dd6910f","repo":"spring-projects/spring-ai","slug":"unsupported-exchange-type-exchange-null-exc","errorCode":null,"errorMessage":"Unsupported exchange type: {exchange != null ? exchange.getClass().getName() : \"null\"} for Async method: {method.getName()} in {method.getDeclaringClass().getName()}","messagePattern":"Unsupported exchange type: (.+?) for Async method: (.+?) in (.+?)","errorType":"validation","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/AsyncMcpPromptMethodCallback.java","lineNumber":83,"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 builds the arguments for the method call, invokes the method, and\n\t * converts the result to a GetPromptResult.\n\t * @param exchange The server exchange, may be null if the method doesn't require it\n\t * @param request The prompt request, must not be null\n\t * @return A Mono that emits the prompt result\n\t * @throws McpError if there is an error invoking the prompt method","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/AsyncMcpPromptMethodCallback.java#L65-L101","documentation":"When the async prompt method declares a McpAsyncServerExchange parameter but the exchange object supplied at invocation is not a McpAsyncServerExchange (it is something else, or null), assignExchangeType throws this IllegalArgumentException. The declared parameter cannot be satisfied, so the invocation aborts with a message naming the actual exchange class (or \"null\").","triggerScenarios":"Method declares McpAsyncServerExchange but buildArgs passes a null or foreign exchange object — e.g. the callback was invoked with no exchange set on the builder, or a custom caller supplied a McpSyncServerExchange/McpTransportContext instead.","commonSituations":"Forgetting to call .exchange(...) on the callback builder in programmatic registration; a test harness invoking apply() without an exchange; sync/async wiring mixups where a sync exchange reaches an async handler.","solutions":["Ensure the async callback builder has an exchange set (builder.exchange(asyncServerExchange)) before use.","Pass a McpAsyncServerExchange when invoking the callback; construct one from the async server if needed.","If a sync exchange is what you actually have, declare McpSyncServerExchange and use the sync callback variant."],"exampleFix":"// before\nvar callback = AsyncMcpPromptMethodCallback.builder().method(m).bean(b).prompt(p).build(); // no exchange\n// after\nvar callback = AsyncMcpPromptMethodCallback.builder().method(m).bean(b).prompt(p)\n    .exchange(asyncServerExchange).build();","handlingStrategy":"type-guard","validationCode":"if (exchange == null) {\n    throw new IllegalStateException(\"Async callback requires a non-null McpAsyncServerExchange\");\n}","typeGuard":"boolean hasAsyncExchange(Object exchange) { return exchange instanceof McpAsyncServerExchange; }","tryCatchPattern":"try {\n    return callback.apply(exchange, request);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"Unsupported exchange type\")) {\n        log.error(\"Exchange missing or wrong type for async prompt callback\", e);\n        throw new IllegalStateException(\"Provide a McpAsyncServerExchange\", e);\n    }\n    throw e;\n}","preventionTips":["Always call .exchange(...) on AsyncMcpPromptMethodCallback.builder() before build().","In tests, construct a real or mocked McpAsyncServerExchange, not null.","Guard apply() call sites with an instanceof check on the exchange."],"tags":["mcp","async","exchange-type","null-argument"],"backgroundTag":"incompatible-source-type","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}