spring-projects/spring-ai · error · IllegalArgumentException

Method must return CreateMessageResult: {methodName} in {cla

Error message

Method must return CreateMessageResult: {methodName} in {className} returns {returnTypeName}

What it means

SyncMcpSamplingMethodCallback.validateReturnType throws IllegalArgumentException when a method registered as a sync sampling callback does not return a CreateMessageResult (or subtype). Unlike the async variant, Mono is not accepted here; the sync path invokes the method and casts the raw result to CreateMessageResult.

Source

Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/sampling/SyncMcpSamplingMethodCallback.java:86

			// Return the result
			return (CreateMessageResult) result;
		}
		catch (Exception e) {
			throw new McpSamplingMethodException("Error invoking sampling method: " + this.method.getName(), e);
		}
	}

	/**
	 * Validates that the method return type is compatible with the sampling 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 (!CreateMessageResult.class.isAssignableFrom(returnType)) {
			throw new IllegalArgumentException("Method must return CreateMessageResult: " + method.getName() + " in "
					+ method.getDeclaringClass().getName() + " returns " + returnType.getName());
		}
	}

	/**
	 * Checks if a parameter type is compatible with the exchange type.
	 * @param paramType The parameter type to check
	 * @return true if the parameter type is compatible with the exchange type, false
	 * otherwise
	 */
	@Override
	protected boolean isExchangeType(Class<?> paramType) {
		// No exchange type for sampling methods
		return false;
	}

	/**
	 * Create a new builder.

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Change the method return type to CreateMessageResult
  2. Wrap the produced value: return new CreateMessageResult(...) inside the handler
  3. If the handler is truly reactive, register it under AsyncSamplingSpecification instead

Example fix

// before
Mono<CreateMessageResult> sample(CreateMessageRequest req) { ... }
// after
CreateMessageResult sample(CreateMessageRequest req) {
    return new CreateMessageResult(buildMessage(req));
}
Defensive patterns

Strategy: validation

Validate before calling

if (!CreateMessageResult.class.isAssignableFrom(method.getReturnType())) {
    throw new IllegalArgumentException(method + " must return CreateMessageResult for sync sampling");
}

Try / catch

try { builder.build(); } catch (IllegalArgumentException e) { log.error("Sync sampling return type invalid: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Registering a sampling method returning String, void, Mono<CreateMessageResult>, or any other type on the sync callback path, then constructing the callback.

Common situations: Using a reactive-style handler method in a SyncSamplingSpecification; forgetting to adapt the return type when migrating from async to sync specs; returning a DTO instead of CreateMessageResult.

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/d2275d6366168059. Report an issue: GitHub.