{"record":{"id":"2d15087681e32200","repo":"spring-projects/spring-ai","slug":"first-parameter-must-be-of-type-double-or-double","errorCode":null,"errorMessage":"First parameter must be of type Double or double: {method.getName()} in {method.getDeclaringClass().getName()} has parameter of type {parameters[0].getType().getName()}","messagePattern":"First parameter must be of type Double or double: (.+?) in (.+?) has parameter of type (.+?)","errorType":"validation","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/progress/AbstractMcpProgressMethodCallback.java","lineNumber":112,"sourceCode":"\t\t\t\t\t\"Method must have either 1 parameter (ProgressNotification) or 3 parameters (Double, String, String): \"\n\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName() + \" has \"\n\t\t\t\t\t\t\t+ parameters.length + \" parameters\");\n\t\t}\n\n\t\t// Check parameter types\n\t\tif (parameters.length == 1) {\n\t\t\t// Single parameter must be ProgressNotification\n\t\t\tif (!ProgressNotification.class.isAssignableFrom(parameters[0].getType())) {\n\t\t\t\tthrow new IllegalArgumentException(\"Single parameter must be of type ProgressNotification: \"\n\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName() + \" has parameter of type \"\n\t\t\t\t\t\t+ parameters[0].getType().getName());\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t// Three parameters must be Double, String, String\n\t\t\tif (!Double.class.isAssignableFrom(parameters[0].getType())\n\t\t\t\t\t&& !double.class.isAssignableFrom(parameters[0].getType())) {\n\t\t\t\tthrow new IllegalArgumentException(\"First parameter must be of type Double or double: \"\n\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName() + \" has parameter of type \"\n\t\t\t\t\t\t+ parameters[0].getType().getName());\n\t\t\t}\n\t\t\tif (!String.class.isAssignableFrom(parameters[1].getType())) {\n\t\t\t\tthrow new IllegalArgumentException(\"Second parameter must be of type String: \" + method.getName()\n\t\t\t\t\t\t+ \" in \" + method.getDeclaringClass().getName() + \" has parameter of type \"\n\t\t\t\t\t\t+ parameters[1].getType().getName());\n\t\t\t}\n\t\t\tif (!String.class.isAssignableFrom(parameters[2].getType())) {\n\t\t\t\tthrow new IllegalArgumentException(\"Third parameter must be of type String: \" + method.getName()\n\t\t\t\t\t\t+ \" in \" + method.getDeclaringClass().getName() + \" has parameter of type \"\n\t\t\t\t\t\t+ parameters[2].getType().getName());\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Builds the arguments array for invoking the method.","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/progress/AbstractMcpProgressMethodCallback.java#L94-L130","documentation":"When an @McpProgress-annotated method declares three parameters, the callback validation requires the first parameter to be a progress value of type Double or double. AbstractMcpProgressMethodCallback.validateParameters throws this IllegalArgumentException when the first parameter has any other type. This ensures the progress ratio (0.0-1.0) can be delivered correctly.","triggerScenarios":"Declaring a progress handler method with exactly three parameters whose first parameter is not Double/double, e.g. (int progress, String token, String message), then registering it via AsyncMcpProgressMethodCallback or SyncMcpProgressMethodCallback builder; validateMethod runs the check at registration time.","commonSituations":"Developers guess the progress callback signature instead of following the (Double progress, String progressToken, String message) contract; using Integer or float for the progress value; porting a two-parameter handler to three parameters and keeping the original first-arg type.","solutions":["Change the first parameter of the progress method to Double (or primitive double).","If the value should be integral, convert at the call site and keep the method signature as (Double, String, String).","If you need fewer parameters, use the two-parameter (Double, String) form instead, which skips this three-parameter check."],"exampleFix":"// before\n@McpProgress\npublic void onProgress(int progress, String token, String msg) { ... }\n// after\n@McpProgress\npublic void onProgress(Double progress, String token, String msg) { ... }","handlingStrategy":"validation","validationCode":"if (method.getParameterCount() == 3) {\n    Class<?> p0 = method.getParameterTypes()[0];\n    if (!Double.class.equals(p0) && !double.class.equals(p0)) {\n        throw new IllegalStateException(method + \" first param must be Double/double\");\n    }\n}","typeGuard":"boolean isValidProgressSignature(Method m) {\n    return m.getParameterCount() != 3\n        || Double.class.isAssignableFrom(m.getParameterTypes()[0]);\n}","tryCatchPattern":"try {\n    registry.register(callbackBuilder.build());\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"First parameter must be\")) {\n        throw new ConfigurationException(\"Fix @McpProgress signature: (Double, String[, String])\", e);\n    }\n    throw e;\n}","preventionTips":["Always copy the documented signature (Double progress, String progressToken) or (Double, String, String).","Add an architecture/unit test that scans @McpProgress methods and asserts their parameter types.","Register callbacks in an integration test so validation failures surface before production."],"tags":["mcp","progress-notification","method-signature","illegal-argument"],"backgroundTag":"invalid-argument-type-mismatch","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}