spring-projects/spring-ai · error · java.lang.IllegalArgumentException
Method cannot have more than one exchange parameter: {method
Error message
Method cannot have more than one exchange parameter: {method.getName()} in {method.getDeclaringClass().getName()} What it means
During prompt-method signature validation, Spring AI MCP's AbstractMcpPromptMethodCallback rejects methods that declare more than one exchange/context parameter recognized as a supported exchange type (e.g. McpSyncServerExchange/McpAsyncServerExchange/McpTransportContext depending on sync/async variant). The framework cannot decide which single exchange instance to bind, so it throws at callback registration time (constructor -> validateMethod -> validateParameters). This is a fail-fast guard: the method can never be invoked correctly.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/AbstractMcpPromptMethodCallback.java:173
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasRequestContextParam = true;
}
else if (McpAsyncRequestContext.class.isAssignableFrom(paramType)) {
if (hasRequestContextParam) {
throw new IllegalArgumentException("Method cannot have more than one request context parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
if (McpPredicates.isNotReactiveReturnType.test(method)) {
throw new IllegalArgumentException(
"Async complete methods should use McpAsyncRequestContext instead of McpSyncRequestContext parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasRequestContextParam = true;
}
else if (isSupportedExchangeOrContextType(paramType)) {
if (hasExchangeParam) {
throw new IllegalArgumentException("Method cannot have more than one exchange parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasExchangeParam = true;
}
else if (GetPromptRequest.class.isAssignableFrom(paramType)) {
if (hasRequestParam) {
throw new IllegalArgumentException("Method cannot have more than one GetPromptRequest parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasRequestParam = true;
}
else if (Map.class.isAssignableFrom(paramType)) {
if (hasMapParam) {
throw new IllegalArgumentException("Method cannot have more than one Map parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasMapParam = true;
}View on GitHub (pinned to 98a7beda4f)
Solutions
- Remove the duplicate exchange parameter; the framework injects the exchange automatically, so keep exactly one (or none).
- If you need transport context plus exchange, ensure only one parameter is of the supported exchange/context type and derive the other from it inside the method.
- Align the parameter with the sync/async variant: use McpSyncServerExchange for sync methods and McpAsyncServerExchange or McpTransportContext for async methods, only once.
Example fix
// before
@McpPrompt(name = "greet")
public String greet(McpSyncServerExchange exchange, McpTransportContext ctx, @McpArg String name) { ... }
// after
@McpPrompt(name = "greet")
public String greet(McpSyncServerExchange exchange, @McpArg String name) { ... } Defensive patterns
Strategy: validation
Validate before calling
long exchangeParams = Arrays.stream(method.getParameters())
.map(Parameter::getType)
.filter(t -> McpSyncServerExchange.class.isAssignableFrom(t)
|| McpAsyncServerExchange.class.isAssignableFrom(t)
|| McpTransportContext.class.isAssignableFrom(t))
.count();
if (exchangeParams > 1) throw new IllegalStateException(method + " declares multiple exchange parameters"); Prevention
- Declare at most one exchange/context parameter per prompt method.
- Remember the framework injects the exchange automatically — you rarely need it at all.
- Run a startup-time test that constructs callbacks for all @McpPrompt beans to fail fast.
When it happens
Trigger: Registering an @McpPrompt-annotated method whose signature contains two parameters assignable to the supported exchange/context type, e.g. `myPrompt(McpSyncServerExchange ex1, McpSyncServerExchange ex2, ...)` or (in async variant) two McpTransportContext parameters. Thrown from validateParameters when isSupportedExchangeOrContextType(paramType) is true and hasExchangeParam is already true.
Common situations: Copy-pasting an exchange parameter while adding a transport context; refactoring to add an extra exchange for progress/cancellation without realizing the framework injects it automatically; merging two handler methods into one and keeping both exchange parameters.
Understand the failure class
Background: "must be a positive integer", "cannot be empty", "invalid argument": how invalid-argument errors work across open-source libraries — this error's family across 33 libraries.
Related errors
- Method cannot have more than one GetPromptRequest parameter:
- Method cannot have more than one Map parameter: {method.getN
- Method must have exactly 1 parameter (List<McpSchema.Prompt>
- Parameter must be of type List<McpSchema.Prompt>:
- Method must have void or Mono<Void> return type:
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/f167c4a94c4aa5fc.
Report an issue: GitHub.