spring-projects/spring-ai · error · ClassCastException
Expected Mono<Void> but got Mono<
Error message
Expected Mono<Void> but got Mono<
What it means
An @McpLogging annotated method declared to return Mono<Void> actually emitted a non-null, non-Void value. The callback flatMaps over the result and throws ClassCastException naming the unexpected element type, because a logging handler must complete without emitting a value. The error message is truncated at 'Mono<' plus the value's class name.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/logging/AsyncMcpLoggingMethodCallback.java:80
// Build arguments for the method call
Object[] args = this.buildArgs(this.method, null, notification);
// Invoke the method
this.method.setAccessible(true);
Object result = this.method.invoke(this.bean, args);
// If the method returns a Mono, handle it
if (result instanceof Mono) {
// We need to handle the case where the Mono is not a Mono<Void>
// This is expected by the test testInvalidMonoReturnType
Mono<?> monoResult = (Mono<?>) result;
// Convert the Mono to a Mono<Void> by checking the value
// If the value is not null (i.e., not Void), throw a ClassCastException
return monoResult.flatMap(value -> {
if (value != null) {
// This will be caught by the test testInvalidMonoReturnType
throw new ClassCastException(
"Expected Mono<Void> but got Mono<" + value.getClass().getName() + ">");
}
return Mono.empty();
}).then();
}
// If the method returns void, return an empty Mono
return Mono.empty();
}
catch (Exception e) {
return Mono.error(new McpLoggingConsumerMethodException(
"Error invoking logging consumer method: " + this.method.getName(), e));
}
}
/**
* Validates that the method return type is compatible with the logging consumer
* callback.
* @param method The method to validateView on GitHub (pinned to 98a7beda4f)
Solutions
- End the handler's reactive chain with .then() or return Mono.empty() so no value is emitted.
- Replace map(...) with flatMap(... -> Mono.empty()) or thenMap-free operators that discard the element.
- If you actually need to return data, the method is not a logging consumer — move the logic elsewhere or change the annotation/callback type.
- Check the test testInvalidMonoReturnType pattern: verify your handler with a unit test that subscribes and asserts the Mono completes empty.
Example fix
// before
public Mono<Void> handleLog(LoggingMessageNotification n) {
return Mono.just(log(n));
}
// after
public Mono<Void> handleLog(LoggingMessageNotification n) {
return Mono.fromRunnable(() -> log(n));
} Defensive patterns
Strategy: validation
Validate before calling
Mono<Void> result = handler.apply(notification);
// verify emptiness before trusting the handler
result.subscribe(v -> { if (v != null) throw new IllegalStateException("handler emitted non-null value"); }); Type guard
static <T> Mono<Void> requireEmpty(Mono<T> mono) {
return mono.flatMap(v -> v == null ? Mono.empty()
: Mono.error(new ClassCastException("Expected Mono<Void> but got Mono<" + v.getClass().getName() + ">")));
} Try / catch
try {
callback.apply(notification).block();
} catch (ClassCastException e) {
logger.error("Logging handler must complete empty: {}", e.getMessage());
} Prevention
- Always end logging handler chains with .then() or Mono.fromRunnable
- Never use map() as the last operator in a Mono<Void> handler
- Unit-test handlers by asserting the Mono completes without emitting
When it happens
Trigger: Registering an async logging handler method whose declared return type is Mono<Void> but whose reactive chain ends with mono.just(something) or map(...) producing a non-null value, then the framework invoking apply() when a logging notification arrives.
Common situations: Returning Mono.just(true) or a mapped result from a logging handler out of habit from other reactive code; a refactor that changed the handler's last operator from then()/empty() to map(); misunderstanding that Mono<Void> means the pipeline must never emit an element.
Related errors
- Expected Mono<Void> but got Mono<
- Method must have void or Mono<Void> return type:
- Method must return Mono<ElicitResult> or Mono<StructuredElic
- 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/6b213b424327981f.
Report an issue: GitHub.