spring-projects/spring-ai · error · IllegalArgumentException

Unsupported exchange type: {exchangeClassName|null} for meth

Error message

Unsupported exchange type: {exchangeClassName|null} for method: {methodName} in {className}

What it means

Thrown by assignExchangeType when an MCP annotated resource method is declared sync but the framework injects an exchange object of an unsupported (async) class, or the exchange is null and no matching parameter binding applies. The library distinguishes Sync vs Async callbacks and only allows the exchange type matching the callback flavor.

Source

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

	@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 extracts URI variable values from the request URI, builds the arguments
	 * for the method call, invokes the method, and converts the result to a
	 * ReadResourceResult.
	 * @param context The transport context, may be null if the method doesn't require it
	 * @param request The resource request, must not be null
	 * @return The resource result
	 * @throws McpError if there is an error invoking the resource method
	 * @throws IllegalArgumentException if the request is null or if URI variable
	 * extraction fails
	 */
	@Override

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Change the method's exchange parameter to the sync exchange type matching the callback (or remove the parameter if not needed)
  2. Register the method/callback on the async server builder if the method uses an async exchange type
  3. Check that the exchange is non-null and of the exact class the sync callback supports (inspect the class name in the message)
  4. Align library versions so the supported exchange classes match those your code imports

Example fix

// before
@McpResource(uri = "file://{path}")
public String read(McpAsyncServerExchange exchange, String path) { ... }
// after
@McpResource(uri = "file://{path}")
public String read(McpSyncServerExchange exchange, String path) { ... }
Defensive patterns

Strategy: validation

Validate before calling

boolean ok = java.util.stream.Stream.of(method.getParameterTypes())
    .allMatch(t -> !McpAsyncServerExchange.class.isAssignableFrom(t));
if (!ok) throw new IllegalStateException("Sync resource method must not take an async exchange");

Type guard

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

Try / catch

try {
    return callback.apply(context, request);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Unsupported exchange type")) {
        // fall back to async callback or re-register method with sync exchange
    }
    throw e;
}

Prevention

When it happens

Trigger: Registering a resource method whose exchange parameter is an async type (e.g. McpAsyncServerExchange or a reactive exchange, or an unexpected type/null) on a sync stateless resource callback (SyncStatelessMcpResourceMethodCallback).

Common situations: Mixing sync and async MCP APIs: annotating a method with an async exchange parameter but registering the bean/handler via the sync server builder; passing null exchange in tests; version drift where a new exchange class is not in the supported list.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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