spring-projects/spring-ai · error · IllegalArgumentException

Unsupported Async exchange type: {exchangeClassName} for Syn

Error message

Unsupported Async exchange type: {exchangeClassName} for Sync method: {methodName} in {className}

What it means

When a sync resource method declares a McpTransportContext parameter, assignExchangeType accepts a transport context or a sync exchange, but throws if the runtime exchange argument is an McpAsyncServerExchange. An async exchange cannot be adapted to a sync method invocation, so the call fails with IllegalArgumentException.

Source

Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/SyncMcpResourceMethodCallback.java:78

		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. Ensure the method is registered only with a McpSyncServer so the runtime exchange is McpSyncServerExchange.
  2. Remove duplicate registrations of the same handler on async servers.
  3. If you truly need async, move the method to an async callback that accepts McpAsyncServerExchange.

Example fix

// before (async server using a sync-declared handler)
McpAsyncServer asyncServer = ...;
asyncServer.addResource(..., (exchange, req) -> syncCallback.apply(exchange, req));

// after
McpSyncServer syncServer = ...;
syncServer.addResource(..., syncCallback);
Defensive patterns

Strategy: type-guard

Validate before calling

Object safeExchangeForSync(Object exchange) {
    if (exchange instanceof McpAsyncServerExchange)
        throw new IllegalArgumentException("Async exchange passed to sync resource callback");
    return exchange;
}

Type guard

static boolean isSyncCompatibleExchange(Object exchange) {
    return !(exchange instanceof McpAsyncServerExchange);
}

Try / catch

try {
    return callback.apply(exchange, request);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Unsupported Async exchange type")) {
        log.error("Registered sync handler is being driven by an async exchange; check server wiring", e);
        throw new IllegalStateException("Handler/server wiring mismatch", e);
    } throw e;
}

Prevention

When it happens

Trigger: A sync server resource method expecting McpTransportContext (or compatible paramType) is invoked with an McpAsyncServerExchange instance as the exchange argument — e.g., the method callback is wired into an async execution path.

Common situations: Registering the same annotated method with both sync and async server providers, or manually calling the callback with an exchange obtained from an async server session.

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