spring-projects/spring-ai · error · IllegalArgumentException

Unsupported exchange type: ${exchangeType} for method: ${met

Error message

Unsupported exchange type: ${exchangeType} for method: ${method} in ${declaringClass}

What it means

The sync stateless prompt callback was handed an exchange object that is neither McpSyncServerExchange, McpAsyncServerExchange, nor a recognized transport context, or the exchange is null in a context where a type is required. assignExchangeType exhausts its instanceof chain and throws IllegalArgumentException naming the actual class (or "null").

Source

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

	@Override
	protected Object assignExchangeType(Class<?> paramType, Object exchange) {

		if (McpTransportContext.class.isAssignableFrom(paramType)) {
			if (exchange instanceof McpTransportContext transportContext) {
				return transportContext;
			}
			else if (exchange instanceof McpSyncServerExchange syncServerExchange) {
				return syncServerExchange.transportContext();
			}
			else if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {
				throw new IllegalArgumentException("Unsupported Async exchange type: "
						+ asyncServerExchange.getClass().getName() + " for Sync method: " + method.getName() + " in "
						+ method.getDeclaringClass().getName());
			}
		}

		throw new IllegalArgumentException(
				"Unsupported exchange type: " + (exchange != null ? exchange.getClass().getName() : "null")
						+ " for method: " + method.getName() + " in " + method.getDeclaringClass().getName());
	}

	/**
	 * Apply the callback to the given context and request.
	 * <p>
	 * This method builds the arguments for the method call, invokes the method, and
	 * converts the result to a GetPromptResult.
	 * @param context The transport context, may be null if the method doesn't require it
	 * @param request The prompt request, must not be null
	 * @return The prompt result
	 * @throws McpError if there is an error invoking the prompt method
	 * @throws IllegalArgumentException if the request is null
	 */
	@Override
	public GetPromptResult apply(McpTransportContext context, GetPromptRequest request) {
		if (request == null) {

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Ensure the caller passes McpSyncServerExchange, McpAsyncServerExchange, or McpTransportContext as expected by the sync stateless callback.
  2. Check that the server type (sync vs async) matches the callback type being invoked.
  3. If exchange may be null, verify your transport setup for the stateless Streamable-Http mode; the request must carry a recognized exchange/context.

Example fix

// before
callback.assignExchangeType(someCustomExchange);

// after
callback.assignExchangeType(new McpSyncServerExchange(transportSession));
Defensive patterns

Strategy: type-guard

Validate before calling

if (exchange == null && callbackRequiresExchange) {
    throw new IllegalArgumentException("Exchange must not be null for stateless prompt callback");
}

Type guard

static boolean isRecognizedExchange(Object exchange) {
    return exchange instanceof McpSyncServerExchange
        || exchange instanceof McpAsyncServerExchange
        || exchange instanceof McpTransportContext;
}

Try / catch

try {
    GetPromptResult result = callback.apply(context, request);
} catch (IllegalArgumentException e) {
    log.error("Unsupported exchange type for stateless prompt callback: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Calling assignExchangeType/apply with an exchange of an unknown class or null in stateless mode where the code path still expects a recognized exchange wrapper; e.g. a custom transport passing a raw context object.

Common situations: Custom transport implementations feeding unexpected exchange types into annotation callbacks; upgrading the MCP SDK and passing a new exchange type the annotation layer doesn't recognize; null exchange with a method that requires context extraction.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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