spring-projects/spring-ai · error · IllegalArgumentException
Method parameters must be exchange, ReadResourceRequest…
Error message
Method parameters must be exchange, ReadResourceRequest, String, McpMeta, or @McpProgressToken when no URI variables are present: ${method} in ${declaringClass} has parameter of type ${paramType} What it means
Thrown when a resource method without URI variables has a parameter whose type is none of the supported ones: exchange types, ReadResourceRequest, String, McpMeta, or a parameter annotated @McpProgressToken. The framework cannot bind arbitrary types for URI-variable-less resource methods, so the method is rejected.
Solutions
- Remove or retype the unsupported parameter to ReadResourceRequest or String
- If the parameter was meant to bind a URI variable, add {var} placeholders to the @McpResource uri and validateParametersWithUriVariables will apply instead
- Annotate progress-related parameters with @McpProgressToken so they are skipped by validation
Example fix
// before
@McpResource(uri = "db://rows")
public String read(Map<String, String> filters) { ... }
// after
@McpResource(uri = "db://rows")
public String read(ReadResourceRequest request) { ... } Defensive patterns
Strategy: validation
Validate before calling
Set<Class<?>> allowed = Set.of(ReadResourceRequest.class, String.class, McpMeta.class,
McpSyncRequestContext.class, McpAsyncRequestContext.class);
for (Parameter p : method.getParameters()) {
if (p.isAnnotationPresent(McpProgressToken.class)) continue;
Class<?> t = p.getType();
if (!allowed.stream().anyMatch(a -> a.isAssignableFrom(t))) {
throw new IllegalStateException("Unsupported param " + t + " in " + method);
}
} Type guard
static boolean isSupportedResourceParam(Parameter p) {
Class<?> t = p.getType();
return p.isAnnotationPresent(McpProgressToken.class)
|| McpMeta.class.isAssignableFrom(t)
|| ReadResourceRequest.class.isAssignableFrom(t)
|| String.class.isAssignableFrom(t);
} Try / catch
try { provider.build(...); }
catch (IllegalArgumentException e) {
if (e.getMessage().contains("parameters must be exchange, ReadResourceRequest, String, McpMeta")) { /* retype or remove the offending param */ }
else throw e;
} Prevention
- Only use framework-supported parameter types in URI-variable-less resource methods
- Annotate progress parameters with @McpProgressToken to have them skipped
- If you need URI variables, add {var} placeholders to the @McpResource uri
When it happens
Trigger: A @McpResource URI contains no {var} placeholders but the handler method includes a parameter of an unsupported type (e.g. Map, custom POJO, Integer) that is not annotated with @McpProgressToken.
Common situations: Reusing a tool-style handler signature for a resource; forgetting that URI variables are only allowed when the URI template has placeholders; passing a custom request object instead of ReadResourceRequest.
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 must have either ReadResourceRequest or String…
- Async complete methods should use McpAsyncRequestContext…
- Async complete methods should use McpAsyncRequestContext…
- ASYNC Providers don't support imperative (non-reactive)…
- Method cannot have more than one CompleteArgument parameter
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/ef1de0c4caa0022e.
Report an issue: GitHub.
Appendix: source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AbstractMcpResourceMethodCallback.java:259
else if (isExchangeOrContextType(paramType)) {
if (hasExchangeParam) {
throw new IllegalArgumentException("Method cannot have more than one exchange parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasExchangeParam = true;
}
else if (ReadResourceRequest.class.isAssignableFrom(paramType)
|| String.class.isAssignableFrom(paramType)) {
if (hasRequestOrUriParam) {
throw new IllegalArgumentException(
"Method cannot have more than one ReadResourceRequest or String parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasRequestOrUriParam = true;
hasValidParams = true;
}
else {
throw new IllegalArgumentException(
"Method parameters must be exchange, ReadResourceRequest, String, McpMeta, or @McpProgressToken when no URI variables are present: "
+ method.getName() + " in " + method.getDeclaringClass().getName()
+ " has parameter of type " + paramType.getName());
}
}
if (!hasValidParams && nonSpecialParamCount > 0) {
throw new IllegalArgumentException(
"Method must have either ReadResourceRequest or String parameter when no URI variables are present: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
}
protected void validateParamType(Class<?> paramType) {
}
/**
* Validates method parameters when URI variables are present. This method providesView on GitHub (pinned to 98a7beda4f)