spring-projects/spring-ai · error · java.lang.IllegalArgumentException

Method must return a Mono<T> where T is one of GetPromptResu

Error message

Method must return a Mono<T> where T is one of GetPromptResult, List<PromptMessage>, List<String>, PromptMessage, or String: {method.getName()} in {method.getDeclaringClass().getName()} returns {returnType.getName()}

What it means

AsyncMcpPromptMethodCallback validates that any @McpPrompt-annotated method handled by the async callback returns a Mono<T>, since async MCP prompt execution is reactive. If the method returns a plain (non-Mono) type, the callback cannot wrap it in the reactive pipeline and throws IllegalArgumentException at registration/validation time.

Source

Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/AsyncMcpPromptMethodCallback.java:157

					.data(ErrorUtils.findCauseUsingPlainJava(e).getMessage())
					.build());
			}
		});
	}

	@Override
	protected boolean isSupportedExchangeOrContextType(Class<?> paramType) {
		return (McpAsyncServerExchange.class.isAssignableFrom(paramType)
				|| McpTransportContext.class.isAssignableFrom(paramType));
	}

	@Override
	protected void validateReturnType(Method method) {
		Class<?> returnType = method.getReturnType();

		// For AsyncMcpPromptMethodCallback, the method must return a Mono
		if (!Mono.class.isAssignableFrom(returnType)) {
			throw new IllegalArgumentException(
					"Method must return a Mono<T> where T is one of GetPromptResult, List<PromptMessage>, "
							+ "List<String>, PromptMessage, or String: " + 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 AsyncMcpPromptMethodCallback instances.
	 * <p>
	 * This builder provides a fluent API for constructing AsyncMcpPromptMethodCallback

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Change the method return type to Mono<T> where T is GetPromptResult, List<PromptMessage>, List<String>, PromptMessage, or String
  2. Wrap the existing return value: return Mono.just(result)
  3. If the method is inherently synchronous, register it with the sync MCP server / SyncMcpPromptMethodCallback instead

Example fix

// before
@McpPrompt(description = "greet")
public String greet(GreetRequest req) { return "hi"; }
// after
@McpPrompt(description = "greet")
public Mono<String> greet(GreetRequest req) { return Mono.just("hi"); }
Defensive patterns

Strategy: validation

Validate before calling

if (!Mono.class.isAssignableFrom(method.getReturnType())) { throw new IllegalStateException(method + " must return Mono<...>"); }

Type guard

boolean returnsMono(Method m) { return Mono.class.isAssignableFrom(m.getReturnType()); }

Try / catch

try { server.addPrompt(callback); } catch (IllegalArgumentException e) { /* fix signature, not retry */ }

Prevention

When it happens

Trigger: Registering a class with an @McpPrompt method whose return type is GetPromptResult, List<PromptMessage>, String, etc. directly (not Mono<...>) while using AsyncMcpPromptMethodCallback (async MCP server).

Common situations: Reusing a sync-style prompt bean on an async MCP server; copying examples from sync MCP docs into a reactive setup; forgetting to wrap the result with Mono.just().

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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