spring-projects/spring-ai · error · java.lang.IllegalArgumentException
Second parameter must be of type String: {method.getName()}
Error message
Second parameter must be of type String: {method.getName()} in {method.getDeclaringClass().getName()} has parameter of type {parameters[1].getType().getName()} What it means
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.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/progress/AbstractMcpProgressMethodCallback.java:117
// Check parameter types
if (parameters.length == 1) {
// Single parameter must be ProgressNotification
if (!ProgressNotification.class.isAssignableFrom(parameters[0].getType())) {
throw new IllegalArgumentException("Single parameter must be of type ProgressNotification: "
+ method.getName() + " in " + method.getDeclaringClass().getName() + " has parameter of type "
+ parameters[0].getType().getName());
}
}
else {
// Three parameters must be Double, String, String
if (!Double.class.isAssignableFrom(parameters[0].getType())
&& !double.class.isAssignableFrom(parameters[0].getType())) {
throw new IllegalArgumentException("First parameter must be of type Double or double: "
+ method.getName() + " in " + method.getDeclaringClass().getName() + " has parameter of type "
+ parameters[0].getType().getName());
}
if (!String.class.isAssignableFrom(parameters[1].getType())) {
throw new IllegalArgumentException("Second parameter must be of type String: " + method.getName()
+ " in " + method.getDeclaringClass().getName() + " has parameter of type "
+ parameters[1].getType().getName());
}
if (!String.class.isAssignableFrom(parameters[2].getType())) {
throw new IllegalArgumentException("Third parameter must be of type String: " + method.getName()
+ " in " + method.getDeclaringClass().getName() + " has parameter of type "
+ parameters[2].getType().getName());
}
}
}
/**
* Builds the arguments array for invoking the method.
* <p>
* This method constructs an array of arguments based on the method's parameter types
* and the available values (exchange, notification).
* @param method The method to build arguments for
* @param exchange The server exchangeView on GitHub (pinned to 98a7beda4f)
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).
Example fix
// before
public void onProgress(Double progress, UUID token, String msg) { ... }
// after
public void onProgress(Double progress, String token, String msg) { ... } Defensive patterns
Strategy: validation
Validate before calling
if (method.getParameterCount() == 3
&& !String.class.equals(method.getParameterTypes()[1])) {
throw new IllegalStateException(method + " second param must be String (progress token)");
} Type guard
boolean hasStringToken(Method m) {
return m.getParameterCount() != 3
|| String.class.isAssignableFrom(m.getParameterTypes()[1]);
} Try / catch
try {
registerProgressHandler(handlerInstance, method);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Second parameter must be")) {
log.error("Progress token parameter must be String: {}", method);
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- First parameter must be of type Double or double: {method.ge
- Third parameter must be of type String: {method.getName()} i
- Single parameter must be of type ElicitRequest:
- Method must have exactly 1 parameter (List<McpSchema.Prompt>
- Parameter must be of type List<McpSchema.Prompt>:
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/284a0c202630f8dc.
Report an issue: GitHub.