spring-projects/spring-ai · error · IllegalArgumentException
Method must have void return type:
Error message
Method must have void return type:
What it means
SyncMcpToolListChangedMethodCallback.validateReturnType() enforces that methods annotated for tool-list-changed callbacks return void, since the consumer is invoked purely for its side effect and there is nowhere to deliver a return value. Any non-void return type is rejected at registration time with an IllegalArgumentException naming the method and its actual return type.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/changed/tool/SyncMcpToolListChangedMethodCallback.java:83
}
catch (Exception e) {
throw new McpToolListChangedConsumerMethodException(
"Error invoking tool list changed consumer method: " + this.method.getName(), e);
}
}
/**
* Validates that the method return type is compatible with the tool list changed
* consumer callback.
* @param method The method to validate
* @throws IllegalArgumentException if the return type is not compatible
*/
@Override
protected void validateReturnType(Method method) {
Class<?> returnType = method.getReturnType();
if (returnType != void.class) {
throw new IllegalArgumentException("Method must have void return type: " + method.getName() + " in "
+ method.getDeclaringClass().getName() + " returns " + returnType.getName());
}
}
/**
* Create a new builder.
* @return A new builder instance
*/
public static Builder builder() {
return new Builder();
}
/**
* Builder for creating SyncMcpToolListChangedMethodCallback instances.
* <p>
* This builder provides a fluent API for constructing
* SyncMcpToolListChangedMethodCallback instances with the required parameters.
*/View on GitHub (pinned to 98a7beda4f)
Solutions
- Change the method return type to void.
- If you need to signal results, log or store state inside the method instead of returning it.
- If you intended reactive semantics, use the async callback variant appropriate for your stack, still honoring the required signature.
Example fix
// before
@McpToolListChanged(clients = "client1")
public boolean onToolsChanged(List<McpSchema.Tool> tools) { return true; }
// after
@McpToolListChanged(clients = "client1")
public void onToolsChanged(List<McpSchema.Tool> tools) { LOG.info("changed"); } Defensive patterns
Strategy: validation
Validate before calling
if (!method.getReturnType().equals(void.class)) {
throw new IllegalStateException("@McpToolListChanged method must return void: " + method.getName());
} Prevention
- Always declare tool-list-changed handlers as void.
- Never return values from consumer-style callbacks.
- Check return types when copying handlers between sync/async variants.
When it happens
Trigger: Registering a @McpToolListChanged handler whose declared return type is anything other than void (e.g., boolean, String, List, Mono) via SyncToolListChangedSpecification / the annotation callback builder.
Common situations: Copying a handler from an async/reactive variant that returns a publisher; accidentally returning a status value from the consumer; IDE auto-generating a non-void stub.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Method must have void or Mono<Void> return type:
- Method must have void return type: " + method.getName() + "
- Method must have void or Mono<Void> return type:
- Method must have void return type:
- Method must have exactly 1 parameter (List<McpSchema.Resourc
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/1314c7e89a3eeff2.
Report an issue: GitHub.