spring-projects/spring-ai · error · java.lang.IllegalArgumentException
Asynchronous progress methods must return void or Mono<Void>
Error message
Asynchronous progress methods must return void or Mono<Void>: {method.getName()} in {method.getDeclaringClass().getName()} returns {returnType.getName()} What it means
Asynchronous (@McpProgress with async callback) methods must return either void or Mono<Void>. validateReturnType throws this IllegalArgumentException as the terminal check when the declared return type is neither, e.g. a concrete type, CompletableFuture, or Flux. It enforces the async progress handler contract at registration time.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/progress/AsyncMcpProgressMethodCallback.java:68
}
if (Mono.class.isAssignableFrom(returnType)) {
// Check if it's Mono<Void>
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType paramType) {
Type[] typeArguments = paramType.getActualTypeArguments();
if (typeArguments.length == 1 && typeArguments[0] == Void.class) {
// Mono<Void> is acceptable
return;
}
else {
throw new IllegalArgumentException("Mono return type must be Mono<Void>: " + method.getName()
+ " in " + method.getDeclaringClass().getName() + " returns " + returnType.getName());
}
}
}
throw new IllegalArgumentException(
"Asynchronous progress methods must return void or Mono<Void>: " + method.getName() + " in "
+ method.getDeclaringClass().getName() + " returns " + returnType.getName());
}
/**
* Apply the progress notification and process it asynchronously.
* <p>
* This method builds the arguments for the method call and invokes the method,
* returning a Mono<Void>.
* @param notification The progress notification, must not be null
* @return A Mono<Void> representing the asynchronous operation
* @throws McpProgressMethodException if there is an error invoking the progress
* method
* @throws IllegalArgumentException if the notification is null
*/
@Override
public Mono<Void> apply(ProgressNotification notification) {
if (notification == null) {View on GitHub (pinned to 98a7beda4f)
Solutions
- Change the method's return type to void.
- Or change it to Mono<Void> and make the body fully reactive, ending with .then().
- If other callers need the current return type, create a dedicated @McpProgress wrapper method returning void that delegates to it.
Example fix
// before
@McpProgress
public CompletableFuture<Void> onProgress(Double p, String token) { ... }
// after
@McpProgress
public Mono<Void> onProgress(Double p, String token) { return CompletableFutureSupplier.then(); } Defensive patterns
Strategy: type-guard
Validate before calling
Class<?> rt = method.getReturnType();
boolean ok = void.class.equals(rt)
|| (Mono.class.equals(rt) && isMonoVoid(method));
if (!ok) throw new IllegalStateException(method + " must return void or Mono<Void>"); Type guard
boolean isValidAsyncProgressReturn(Method m) {
Class<?> rt = m.getReturnType();
if (void.class.equals(rt) || Void.class.equals(rt)) return true;
return Mono.class.equals(rt) && isMonoVoid(m);
} Try / catch
try {
registerAsyncHandler(method, bean);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Asynchronous progress methods must return")) {
throw new ConfigurationException("Change return type to void or Mono<Void>", e);
}
throw e;
} Prevention
- Never reuse arbitrary service methods as async progress handlers without checking the return type.
- Avoid CompletableFuture/Flux in progress handlers — this API only supports void and Mono<Void>.
- Document the allowed signatures next to the @McpProgress usage in your codebase.
When it happens
Trigger: Registering an async progress callback whose method returns boolean, CompletableFuture<Void>, Flux<Void>, ProgressNotification, or any other non-void/non-Mono<Void> type.
Common situations: Reusing an existing service method as a progress handler without adapting its return type; expecting async results via CompletableFuture out of habit; returning the notification object for chaining.
Related errors
- Mono return type must be Mono<Void>: {method.getName()} in {
- Synchronous progress methods must return void: {method.getNa
- Method must return either ReadResourceResult, List<ResourceC
- Expected Mono<Void> but got Mono<
- Method must have void or Mono<Void> return type:
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/ff339075b28ca3f4.
Report an issue: GitHub.