spring-projects/spring-ai · error · IllegalArgumentException
Stateless Streamable-Http prompt method must not declare par
Error message
Stateless Streamable-Http prompt method must not declare parameter of type: {paramTypeName}. Use McpTransportContext instead. Method: {methodName} in {className} What it means
SyncStatelessMcpResourceMethodCallback.validateParamType rejects resource methods used in stateless Streamable-HTTP mode that declare McpSyncServerExchange or McpAsyncServerExchange parameters. Stateless handling has no server session/exchange; only McpTransportContext is available, so the method fails registration with IllegalArgumentException.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/SyncStatelessMcpResourceMethodCallback.java:64
* @author Vadzim Shurmialiou
* @author Craig Walls
*/
public final class SyncStatelessMcpResourceMethodCallback extends AbstractMcpResourceMethodCallback
implements BiFunction<McpTransportContext, ReadResourceRequest, ReadResourceResult> {
private SyncStatelessMcpResourceMethodCallback(Builder builder) {
super(builder.method, builder.bean, builder.uri, builder.name, builder.description, builder.mimeType,
builder.resultConverter, builder.uriTemplateManagerFactory, builder.contentType, builder.meta);
this.validateMethod(this.method);
}
@Override
protected void validateParamType(Class<?> paramType) {
if (McpSyncServerExchange.class.isAssignableFrom(paramType)
|| McpAsyncServerExchange.class.isAssignableFrom(paramType)) {
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: "View on GitHub (pinned to 98a7beda4f)
Solutions
- Replace McpSyncServerExchange/McpAsyncServerExchange parameters with McpTransportContext.
- Remove the exchange parameter entirely if the handler does not need transport metadata.
- If exchange state is genuinely required, run the server in stateful mode instead of stateless Streamable-HTTP.
Example fix
// before
@McpResource(uri = "data://{id}")
public String get(McpSyncServerExchange exchange, String id) { ... }
// after
@McpResource(uri = "data://{id}")
public String get(McpTransportContext context, String id) { ... } Defensive patterns
Strategy: type-guard
Validate before calling
void checkStatelessParams(Class<?>[] paramTypes) {
for (Class<?> p : paramTypes) {
if (McpSyncServerExchange.class.isAssignableFrom(p) || McpAsyncServerExchange.class.isAssignableFrom(p))
throw new IllegalArgumentException("Stateless methods must use McpTransportContext, not " + p.getName());
}
} Type guard
static boolean validStatelessParam(Class<?> p) {
return !McpSyncServerExchange.class.isAssignableFrom(p)
&& !McpAsyncServerExchange.class.isAssignableFrom(p);
} Try / catch
try {
statelessServer.addResource(resource, callback);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("Use McpTransportContext instead")) {
log.error("Stateless Streamable-HTTP handler cannot take server exchange; switch to McpTransportContext", e);
} throw e;
} Prevention
- Maintain separate resource classes for stateful and stateless server deployments.
- Add an ArchUnit rule banning exchange parameters in packages registered with the stateless server.
- When migrating to stateless mode, sweep all @McpResource signatures for exchange parameters first.
When it happens
Trigger: Configuring an MCP server in stateless Streamable-HTTP mode while an @McpResource method signature includes either exchange type. Validation fires when the method callback is created.
Common situations: Reusing the same annotated resource class across stateful and stateless server setups, or migrating a stateful server to stateless mode for horizontal scaling without removing exchange parameters.
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
- Stateless Streamable-Http prompt method must not declare par
- Stateless Streamable-Http prompt method must not declare par
- Sync prompt method must not declare parameter of type: {para
- Method must return either ReadResourceResult, List<ResourceC
- Unsupported Async exchange type: {exchangeClassName} for Syn
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/ff412e58292bed8e.
Report an issue: GitHub.