{"record":{"id":"48092557c4106142","repo":"spring-projects/spring-ai","slug":"method-must-have-parameters-for-all-uri-variables","errorCode":null,"errorMessage":"Method must have parameters for all URI variables. Expected ${expected} URI variable parameters, but found ${found}: ${method} in ${declaringClass}. URI variables: ${uriVariables}","messagePattern":"Method must have parameters for all URI variables\\. Expected (.+?) URI variable parameters, but found (.+?): (.+?) in (.+?)\\. URI variables: (.+?)","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":365,"sourceCode":"\t\t\tthrow new IllegalArgumentException(\"Method cannot have more than one ReadResourceRequest parameter: \"\n\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t}\n\n\t\t// Check if we have more than one meta parameter\n\t\tif (metaParamCount > 1) {\n\t\t\tthrow new IllegalArgumentException(\"Method cannot have more than one McpMeta parameter: \" + method.getName()\n\t\t\t\t\t+ \" in \" + method.getDeclaringClass().getName());\n\t\t}\n\n\t\t// Calculate how many parameters should be for URI variables\n\t\tint requestContextParamCount = hasRequestContextParam ? 1 : 0;\n\t\tint specialParamCount = exchangeParamCount + requestParamCount + progressTokenParamCount + metaParamCount\n\t\t\t\t+ requestContextParamCount;\n\t\tint uriVarParamCount = parameters.length - specialParamCount;\n\n\t\t// Check if we have the right number of parameters for URI variables\n\t\tif (uriVarParamCount != this.uriVariables.size()) {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Method must have parameters for all URI variables. Expected \" + this.uriVariables.size()\n\t\t\t\t\t\t\t+ \" URI variable parameters, but found \" + uriVarParamCount + \": \" + method.getName()\n\t\t\t\t\t\t\t+ \" in \" + method.getDeclaringClass().getName() + \". URI variables: \" + this.uriVariables);\n\t\t}\n\n\t\t// Check that all non-special parameters are String type (for URI variables)\n\t\tfor (Parameter param : parameters) {\n\t\t\t// Skip @McpProgressToken annotated parameters\n\t\t\tif (param.isAnnotationPresent(McpProgressToken.class)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tClass<?> paramType = param.getType();\n\t\t\tif (!McpSyncRequestContext.class.isAssignableFrom(paramType)\n\t\t\t\t\t&& !McpAsyncRequestContext.class.isAssignableFrom(paramType) && !isExchangeOrContextType(paramType)\n\t\t\t\t\t&& !ReadResourceRequest.class.isAssignableFrom(paramType)\n\t\t\t\t\t&& !McpMeta.class.isAssignableFrom(paramType) && !String.class.isAssignableFrom(paramType)) {\n\t\t\t\tthrow new IllegalArgumentException(\"URI variable parameters must be of type String: \" + method.getName()","sourceCodeStart":347,"sourceCodeEnd":383,"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#L347-L383","documentation":"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.","triggerScenarios":"@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().","commonSituations":"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.","solutions":["Make the method declare exactly one String parameter per URI variable in the template, in order","Fix the @McpResource uri template so its variables match the method signature","Verify the uriVariables supplied to the builder/annotation processing are the ones parsed from the actual URI"],"exampleFix":"// before\n@McpResource(uri = \"db://{table}/{id}\")\npublic String read(String table) { ... }\n// after\n@McpResource(uri = \"db://{table}/{id}\")\npublic String read(String table, String id) { ... }","handlingStrategy":"validation","validationCode":"List<String> vars = extractUriVariables(uri); // names inside {…}\nList<Class<?>> uriParams = Arrays.stream(m.getParameters()).map(Parameter::getType)\n    .filter(t -> !isSpecialParam(t)).toList();\nif (uriParams.size() != vars.size()) throw new IllegalStateException(\"URI \" + uri + \" expects \" + vars.size() + \" params, method has \" + uriParams.size());","typeGuard":null,"tryCatchPattern":"try {\n    registerResources(callbacks);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"URI variables\")) {\n        throw new ConfigurationException(\"Fix @McpResource uri template vs method signature: \" + e.getMessage(), e);\n    }\n    throw e;\n}","preventionTips":["Change the template and signature together in one commit","Keep URI variable parameters as plain String in order","Add a unit test that builds a callback for every @McpResource method at startup"],"tags":["mcp","java","validation","uri-template","parameter-count"],"backgroundTag":"missing-required-argument","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}