spring-projects/spring-ai · error · IllegalArgumentException

Method must have parameters for all URI variables. Expected

Error message

Method must have parameters for all URI variables. Expected ${expected} URI variable parameters, but found ${found}: ${method} in ${declaringClass}. URI variables: ${uriVariables}

What it means

Thrown when the number of method parameters left after removing 'special' parameters (exchange, ReadResourceRequest, @McpProgressToken, McpMeta, request context) does not equal the number of URI variables extracted from the resource URI template. Every URI variable in the @McpResource uri must have exactly one corresponding String parameter.

Source

Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AbstractMcpResourceMethodCallback.java:365

			throw new IllegalArgumentException("Method cannot have more than one ReadResourceRequest parameter: "
					+ method.getName() + " in " + method.getDeclaringClass().getName());
		}

		// Check if we have more than one meta parameter
		if (metaParamCount > 1) {
			throw new IllegalArgumentException("Method cannot have more than one McpMeta parameter: " + method.getName()
					+ " in " + method.getDeclaringClass().getName());
		}

		// Calculate how many parameters should be for URI variables
		int requestContextParamCount = hasRequestContextParam ? 1 : 0;
		int specialParamCount = exchangeParamCount + requestParamCount + progressTokenParamCount + metaParamCount
				+ requestContextParamCount;
		int uriVarParamCount = parameters.length - specialParamCount;

		// Check if we have the right number of parameters for URI variables
		if (uriVarParamCount != this.uriVariables.size()) {
			throw new IllegalArgumentException(
					"Method must have parameters for all URI variables. Expected " + this.uriVariables.size()
							+ " URI variable parameters, but found " + uriVarParamCount + ": " + method.getName()
							+ " in " + method.getDeclaringClass().getName() + ". URI variables: " + this.uriVariables);
		}

		// Check that all non-special parameters are String type (for URI variables)
		for (Parameter param : parameters) {
			// Skip @McpProgressToken annotated parameters
			if (param.isAnnotationPresent(McpProgressToken.class)) {
				continue;
			}

			Class<?> paramType = param.getType();
			if (!McpSyncRequestContext.class.isAssignableFrom(paramType)
					&& !McpAsyncRequestContext.class.isAssignableFrom(paramType) && !isExchangeOrContextType(paramType)
					&& !ReadResourceRequest.class.isAssignableFrom(paramType)
					&& !McpMeta.class.isAssignableFrom(paramType) && !String.class.isAssignableFrom(paramType)) {
				throw new IllegalArgumentException("URI variable parameters must be of type String: " + method.getName()

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Make the method declare exactly one String parameter per URI variable in the template, in order
  2. Fix the @McpResource uri template so its variables match the method signature
  3. Verify the uriVariables supplied to the builder/annotation processing are the ones parsed from the actual URI

Example fix

// before
@McpResource(uri = "db://{table}/{id}")
public String read(String table) { ... }
// after
@McpResource(uri = "db://{table}/{id}")
public String read(String table, String id) { ... }
Defensive patterns

Strategy: validation

Validate before calling

List<String> vars = extractUriVariables(uri); // names inside {…}
List<Class<?>> uriParams = Arrays.stream(m.getParameters()).map(Parameter::getType)
    .filter(t -> !isSpecialParam(t)).toList();
if (uriParams.size() != vars.size()) throw new IllegalStateException("URI " + uri + " expects " + vars.size() + " params, method has " + uriParams.size());

Try / catch

try {
    registerResources(callbacks);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("URI variables")) {
        throw new ConfigurationException("Fix @McpResource uri template vs method signature: " + e.getMessage(), e);
    }
    throw e;
}

Prevention

When it happens

Trigger: @McpResource(uri = "db://{table}/{id}") with a method that has only one String parameter (or three), or with an extra non-special parameter that is counted as a URI variable slot; validateParametersWithUriVariables compares uriVarParamCount to this.uriVariables.size().

Common situations: Adding a path segment to the URI without adding a method parameter; typos making a variable unrecognized; passing the wrong uriVariables list into the resource callback builder; changing a method signature without updating the template.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/48092557c4106142. Report an issue: GitHub.