{"record":{"id":"284a0c202630f8dc","repo":"spring-projects/spring-ai","slug":"second-parameter-must-be-of-type-string-method-g","errorCode":null,"errorMessage":"Second parameter must be of type String: {method.getName()} in {method.getDeclaringClass().getName()} has parameter of type {parameters[1].getType().getName()}","messagePattern":"Second parameter must be of type String: (.+?) 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":117,"sourceCode":"\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.\n\t * <p>\n\t * This method constructs an array of arguments based on the method's parameter types\n\t * and the available values (exchange, notification).\n\t * @param method The method to build arguments for\n\t * @param exchange The server exchange","sourceCodeStart":99,"sourceCodeEnd":135,"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#L99-L135","documentation":"For three-parameter progress methods, the second parameter must be a String (the progress token identifying the client request). validateParameters throws this IllegalArgumentException when parameters[1] is not String-assignable. The token is required so the progress notification can be routed to the correct client request.","triggerScenarios":"Registering a progress callback with signature like (Double progress, UUID token, String message) or (Double, String token, String message) where token is a non-String type such as UUID, Object, or StringBuilder.","commonSituations":"Modeling the progress token as UUID or a custom token class; copy-pasting signatures from other callback APIs that use typed tokens; refactoring that swapped parameter order so a String landed in slot 0 and a Double in slot 1.","solutions":["Change the second parameter to java.lang.String.","If using a custom token type, convert it to String via toString() in the caller and accept String in the method.","Ensure the parameter order is exactly (Double progress, String progressToken, String message)."],"exampleFix":"// before\npublic void onProgress(Double progress, UUID token, String msg) { ... }\n// after\npublic void onProgress(Double progress, String token, String msg) { ... }","handlingStrategy":"validation","validationCode":"if (method.getParameterCount() == 3\n        && !String.class.equals(method.getParameterTypes()[1])) {\n    throw new IllegalStateException(method + \" second param must be String (progress token)\");\n}","typeGuard":"boolean hasStringToken(Method m) {\n    return m.getParameterCount() != 3\n        || String.class.isAssignableFrom(m.getParameterTypes()[1]);\n}","tryCatchPattern":"try {\n    registerProgressHandler(handlerInstance, method);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Second parameter must be\")) {\n        log.error(\"Progress token parameter must be String: {}\", method);\n    }\n    throw e;\n}","preventionTips":["Keep the progress token as java.lang.String; convert custom token types with toString() before invoking.","Maintain parameter order exactly (Double, String, String).","Validate annotated methods in a startup test."],"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"}