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 validate

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. End the handler's reactive chain with .then() or return Mono.empty() so no value is emitted.
  2. Replace map(...) with flatMap(... -> Mono.empty()) or thenMap-free operators that discard the element.
  3. If you actually need to return data, the method is not a logging consumer — move the logic elsewhere or change the annotation/callback type.
  4. 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

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


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