spring-projects/spring-ai · error · IllegalArgumentException

Unsupported Async exchange type: ${exchangeType} for Sync me

Error message

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

What it means

The sync stateless prompt callback received an McpAsyncServerExchange where it expected a sync (or context-less) exchange. Sync callbacks can only work with McpSyncServerExchange; an async exchange is incompatible with a synchronous method invocation, so assignExchangeType throws IllegalArgumentException naming the offending exchange type.

Source

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

			throw new IllegalArgumentException(
					"Stateless Streamable-Http prompt method must not declare parameter of type: " + paramType.getName()
							+ ". Use McpTransportContext 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());
			}
		}

		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

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Register the annotated methods with a sync server (McpSyncServer) so sync callbacks receive McpSyncServerExchange.
  2. Remove mixed async server wiring that produces McpAsyncServerExchange for the same methods.
  3. If the method must stay sync, ensure the exchange passed down is sync or null-in-stateless mode per framework conventions.

Example fix

// before (async exchange routed to sync callback)
McpAsyncServerExchange asyncExchange = ...;
callback.assignExchangeType(asyncExchange);

// after (sync exchange)
McpSyncServerExchange syncExchange = ...;
callback.assignExchangeType(syncExchange);
Defensive patterns

Strategy: type-guard

Validate before calling

if (exchange instanceof McpAsyncServerExchange) {
    throw new IllegalStateException("Async exchange routed to sync callback - fix server wiring");
}

Type guard

static boolean isSyncExchange(Object exchange) {
    return exchange instanceof McpSyncServerExchange || exchange == null;
}

Try / catch

try {
    callback.assignExchangeType(exchange);
} catch (IllegalArgumentException e) {
    throw new IllegalStateException("Sync/async exchange mismatch in MCP wiring", e);
}

Prevention

When it happens

Trigger: Invoking a sync stateless prompt callback while passing an exchange object of type McpAsyncServerExchange; typically a wiring/configuration mistake where an async exchange is routed to a sync callback handler.

Common situations: Mixing async and sync annotated handler registrations in one application; custom code calling the callback's apply/assignExchangeType path with an exchange obtained from an async server instance.

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