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

  1. Change the method return type to void.
  2. If you need to signal results, log or store state inside the method instead of returning it.
  3. 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

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


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