spring-projects/spring-ai · error · IllegalArgumentException

Unsupported Async exchange type: ${exchangeType} for Async m

Error message

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

What it means

Thrown by AsyncMcpResourceMethodCallback.assignExchangeType when the annotated async method expects a McpTransportContext parameter but the runtime exchange object is a McpSyncServerExchange. An async callback cannot be driven by a sync exchange, so it cannot supply a matching transport context and fails fast.

Source

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

	@Override
	protected void validateParamType(Class<?> paramType) {

		if (McpSyncServerExchange.class.isAssignableFrom(paramType)) {
			throw new IllegalArgumentException("Async prompt method must not declare parameter of type: "
					+ paramType.getName() + ". Use McpAsyncServerExchange 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) {
				throw new IllegalArgumentException("Unsupported Async exchange type: "
						+ syncServerExchange.getClass().getName() + " for Async method: " + method.getName() + " in "
						+ method.getDeclaringClass().getName());

			}
			else if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {
				return asyncServerExchange.transportContext();
			}
		}
		else if (McpAsyncServerExchange.class.isAssignableFrom(paramType)) {
			if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {
				return asyncServerExchange;
			}

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

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Route the request through the async server/spec builder so an McpAsyncServerExchange (or McpTransportContext) is passed.
  2. If the method should be sync, move it to a sync callback registration matching the exchange you actually have.
  3. Audit the server configuration so each annotated provider is attached to exactly one server flavor consistent with its exchange parameter.

Example fix

// before (sync path driving async callback)
syncServer.callResource(...); // exchange = McpSyncServerExchange

// after
asyncServer.callResource(...); // exchange = McpAsyncServerExchange / McpTransportContext
Defensive patterns

Strategy: try-catch

Validate before calling

if (exchange instanceof McpSyncServerExchange) {
    throw new IllegalStateException("Async callback invoked from sync path");
}

Type guard

static boolean isSyncExchange(Object ex) { return ex instanceof McpSyncServerExchange; }

Try / catch

try {
    return callback.call(exchange, request);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("Unsupported Async exchange type")) {
        throw new IllegalStateException("Routed async callback through sync dispatcher", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Invoking an @McpResource method callback (declared to take McpTransportContext) that was routed through a sync server call path, so the exchange argument passed to assignExchangeType is a McpSyncServerExchange instance.

Common situations: Registering the same annotated resource provider on both a sync and an async server and calling it via the sync path; wiring the async callback into a sync spec builder by mistake.

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