spring-projects/spring-ai · error · IllegalArgumentException
Method cannot have more than one exchange parameter: {method
Error message
Method cannot have more than one exchange parameter: {method} in {class} What it means
Thrown when a @McpComplete method declares more than one MCP exchange parameter (a type recognized by isExchangeType, e.g. McpSyncServerExchange/McpAsyncServerExchange). At most one exchange parameter is allowed.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/complete/AbstractMcpCompleteMethodCallback.java:234
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
if (McpPredicates.isNotReactiveReturnType.test(method)) {
throw new IllegalArgumentException(
"Sync complete methods should use McpSyncRequestContext instead of McpAsyncRequestContext parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasRequestContextParam = true;
}
else if (McpTransportContext.class.isAssignableFrom(paramType)) {
if (hasTransportContext) {
throw new IllegalArgumentException("Method cannot have more than one transport context parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasTransportContext = true;
}
else if (isExchangeType(paramType)) {
if (hasExchangeParam) {
throw new IllegalArgumentException("Method cannot have more than one exchange parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasExchangeParam = true;
}
else if (CompleteRequest.class.isAssignableFrom(paramType)) {
if (hasRequestParam) {
throw new IllegalArgumentException("Method cannot have more than one CompleteRequest parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasRequestParam = true;
}
else if (CompleteRequest.CompleteArgument.class.isAssignableFrom(paramType)) {
if (hasArgumentParam) {
throw new IllegalArgumentException("Method cannot have more than one CompleteArgument parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasArgumentParam = true;
}View on GitHub (pinned to 98a7beda4f)
Solutions
- Remove the extra exchange parameter, keeping a single exchange whose type matches the method's sync/async style
- Rebuild the callback
Example fix
// before
public String complete(McpSyncServerExchange e1, McpAsyncServerExchange e2, CompleteRequest req) { ... }
// after
public String complete(McpSyncServerExchange exchange, CompleteRequest req) { ... } Defensive patterns
Strategy: validation
Validate before calling
long ex = Arrays.stream(m.getParameterTypes()).filter(this::isExchangeType).count();
if (ex > 1) throw new IllegalStateException("Only one exchange parameter allowed: " + m); Try / catch
try {
callbackBuilder.build();
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("more than one exchange")) { /* remove duplicate exchange param */ }
throw e;
} Prevention
- Keep exactly one exchange parameter, matching sync/async style
- Don't mix McpSyncServerExchange and McpAsyncServerExchange in one signature
When it happens
Trigger: A method signature includes two exchange-typed parameters.
Common situations: Mixing sync and async exchange parameters in one signature; incremental refactors leaving the old exchange in place.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Method cannot have more than one transport context parameter
- Method cannot have more than one CompleteRequest parameter:
- Method cannot have more than one CompleteArgument parameter:
- Method cannot have more than one exchange parameter: ${metho
- Method cannot have more than one @McpProgressToken parameter
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/12526dfd34deef67.
Report an issue: GitHub.