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

Unsupported Async exchange type: {asyncServerExchange.getCla

Error message

Unsupported Async exchange type: {asyncServerExchange.getClass().getName()} for Sync method: {method.getName()} in {method.getDeclaringClass().getName()}

What it means

When a sync prompt method declares an McpTransportContext parameter, assignExchangeType converts the incoming exchange into a context. An McpAsyncServerExchange arriving at a Sync method cannot be converted, so the callback throws IllegalArgumentException naming the unsupported async exchange type.

Source

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

		if (McpAsyncServerExchange.class.isAssignableFrom(paramType)) {
			throw new IllegalArgumentException("Sync prompt method must not declare parameter of type: "
					+ paramType.getName() + ". Use McpSyncServerExchange instead." + " Method: " + this.method.getName()
					+ " in " + this.method.getDeclaringClass().getName());
		}
	}

	@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());
			}
		}
		else if (McpSyncServerExchange.class.isAssignableFrom(paramType)) {
			if (exchange instanceof McpSyncServerExchange syncServerExchange) {
				return syncServerExchange;
			}

			throw new IllegalArgumentException(
					"Unsupported exchange type: " + (exchange != null ? exchange.getClass().getName() : "null")
							+ " 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());
	}

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Register the bean with the sync MCP server so a McpSyncServerExchange is supplied
  2. Use a separate bean/callback for the async server
  3. Match transport provider and server types (sync provider with McpServer.sync, async with McpServer.async)

Example fix

// before
McpServer.async(provider).prompts(syncCallback).build();
// after
McpServer.sync(provider).prompts(syncCallback).build();
Defensive patterns

Strategy: type-guard

Validate before calling

if (exchange instanceof McpAsyncServerExchange) throw new IllegalStateException("Sync callback given async exchange");

Type guard

boolean syncCompatible(Object e) { return e instanceof McpTransportContext || e instanceof McpSyncServerExchange; }

Try / catch

try { r = callback.apply(exchange, req); } catch (IllegalArgumentException e) { throw new McpError("Sync/async exchange mismatch"); }

Prevention

When it happens

Trigger: A SyncMcpPromptMethodCallback-handled method with an McpTransportContext parameter is invoked while the dispatch path supplies an McpAsyncServerExchange.

Common situations: Registering the same annotated bean with both sync and async servers; wiring an async transport provider to a sync server.

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