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 exchange

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Change the second parameter to java.lang.String.
  2. If using a custom token type, convert it to String via toString() in the caller and accept String in the method.
  3. 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

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


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