{"record":{"id":"857fe2630159f60d","repo":"spring-projects/spring-ai","slug":"method-cannot-have-more-than-one-exchange-paramete-857fe2","errorCode":null,"errorMessage":"Method cannot have more than one exchange parameter: ${method} in ${declaringClass}","messagePattern":"Method cannot have more than one exchange parameter: (.+?) in (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AbstractMcpResourceMethodCallback.java","lineNumber":243,"sourceCode":"\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t\t\t}\n\t\t\t\tif (McpPredicates.isNotReactiveReturnType.test(method)) {\n\t\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\t\"Async complete methods should use McpAsyncRequestContext instead of McpSyncRequestContext parameter: \"\n\t\t\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t\t\t}\n\t\t\t\thasRequestContextParam = true;\n\t\t\t}\n\t\t\telse if (McpMeta.class.isAssignableFrom(paramType)) {\n\t\t\t\tif (hasMetaParam) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"Method cannot have more than one McpMeta parameter: \"\n\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t\t\t}\n\t\t\t\thasMetaParam = true;\n\t\t\t}\n\t\t\telse if (isExchangeOrContextType(paramType)) {\n\t\t\t\tif (hasExchangeParam) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"Method cannot have more than one exchange parameter: \"\n\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t\t\t}\n\t\t\t\thasExchangeParam = true;\n\t\t\t}\n\t\t\telse if (ReadResourceRequest.class.isAssignableFrom(paramType)\n\t\t\t\t\t|| String.class.isAssignableFrom(paramType)) {\n\t\t\t\tif (hasRequestOrUriParam) {\n\t\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\t\"Method cannot have more than one ReadResourceRequest or String parameter: \"\n\t\t\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t\t\t}\n\t\t\t\thasRequestOrUriParam = true;\n\t\t\t\thasValidParams = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\"Method parameters must be exchange, ReadResourceRequest, String, McpMeta, or @McpProgressToken when no URI variables are present: \"\n\t\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName()","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AbstractMcpResourceMethodCallback.java#L225-L261","documentation":"Thrown when a resource method declares more than one exchange-type parameter (types matched by isExchangeOrContextType, e.g. McpSyncServerExchange/McpAsyncServerExchange). At most one exchange object is provided per call, so duplicates are ambiguous and rejected at registration.","triggerScenarios":"A @McpResource method (no URI variables) has two parameters of an exchange/context type; triggered when hasExchangeParam is already true and a second exchange-typed parameter is encountered.","commonSituations":"Copy-pasting an older exchange-based signature alongside a new one; migrating between exchange-style and context-style APIs and keeping both parameters.","solutions":["Keep a single exchange parameter and drop the duplicate","Prefer the current McpSyncRequestContext/McpAsyncRequestContext style if migrating from raw exchange parameters"],"exampleFix":"// before\npublic String read(McpSyncServerExchange ex1, McpSyncServerExchange ex2) { ... }\n// after\npublic String read(McpSyncServerExchange ex) { ... }","handlingStrategy":"validation","validationCode":"long exCount = Arrays.stream(method.getParameterTypes())\n    .filter(t -> McpSyncServerExchange.class.isAssignableFrom(t) || McpAsyncServerExchange.class.isAssignableFrom(t))\n    .count();\nif (exCount > 1) throw new IllegalStateException(method + \" declares \" + exCount + \" exchange parameters\");","typeGuard":null,"tryCatchPattern":"try { provider.build(...); }\ncatch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"more than one exchange parameter\")) { /* keep a single exchange param */ }\n  else throw e;\n}","preventionTips":["Use one exchange (or context) parameter per resource method, never both styles together","Migrate fully off raw exchange parameters if using the context API"],"tags":["mcp","spring-ai","duplicate-parameter","exchange"],"backgroundTag":"invalid-argument","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"}