spring-projects/spring-ai · error · IllegalStateException

Method must return ElicitResult or StructuredElicitResult:

Error message

Method must return ElicitResult or StructuredElicitResult: 

What it means

Thrown as IllegalStateException by SyncMcpElicitationMethodCallback.apply when the sync handler method's declared return type is neither ElicitResult nor StructuredElicitResult. Unlike the async variant (which validates return type upfront), the sync callback checks the actual returned object at invocation time; simple types and custom objects are a TODO.

Source

Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/elicitation/SyncMcpElicitationMethodCallback.java:92

				var content = structuredElicitResult.structuredContent() != null
						? jsonHelper.convertToMap(structuredElicitResult.structuredContent()) : null;

				return ElicitResult.builder(structuredElicitResult.action())
					.content(content)
					.meta(structuredElicitResult.meta())
					.build();
			}
			else if (this.method.getReturnType().isAssignableFrom(ElicitResult.class)) {
				// If the method returns ElicitResult, return it directly
				return (ElicitResult) result;

			}
			else {

				// TODO add support for methods returning simple types or Objects of
				// elicitation schema type.

				throw new IllegalStateException("Method must return ElicitResult or StructuredElicitResult: "
						+ this.method.getName() + " in " + this.method.getDeclaringClass().getName() + " returns "
						+ this.method.getReturnType().getName());
			}
		}
		catch (Exception e) {
			throw new McpElicitationMethodException("Error invoking elicitation method: " + this.method.getName(), e);
		}
	}

	/**
	 * Validates that the method return type is compatible with the elicitation callback.
	 * @param method The method to validate
	 * @throws IllegalArgumentException if the return type is not compatible
	 */
	@Override
	protected void validateReturnType(Method method) {
		Class<?> returnType = method.getReturnType();

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Change the handler to return ElicitResult, e.g. ElicitResult.builder()...build() with the accepted action and content map
  2. Return StructuredElicitResult for typed/structured responses
  3. Wrap simple values manually: new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("value", myValue))
  4. Note validateReturnType typically catches this earlier; if you see it at runtime, the method was registered without return-type validation

Example fix

// before
@McpElicitation
public String confirm(ElicitRequest request) { return "yes"; }

// after
@McpElicitation
public ElicitResult confirm(ElicitRequest request) {
    return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("answer", "yes"));
}
Defensive patterns

Strategy: validation

Validate before calling

Class<?> rt = handlerMethod.getReturnType();
if (!ElicitResult.class.isAssignableFrom(rt) && !StructuredElicitResult.class.isAssignableFrom(rt)) {
    throw new IllegalArgumentException("Sync handler must return ElicitResult or StructuredElicitResult: " + handlerMethod);
}

Type guard

boolean isValidSyncReturnType(Method m) {
    return ElicitResult.class.isAssignableFrom(m.getReturnType())
        || StructuredElicitResult.class.isAssignableFrom(m.getReturnType());
}

Try / catch

try {
    return callback.apply(request);
} catch (IllegalStateException e) {
    log.error("Handler returns unsupported type: {}", e.getMessage());
    throw e;
}

Prevention

When it happens

Trigger: A sync @McpElicitation handler returning String, Map, custom POJO, void, or any other type: public String confirm(ElicitRequest req). The method must return ElicitResult or StructuredElicitResult.

Common situations: Developers expect the framework to wrap returned simple values into an ElicitResult automatically (that support is not implemented), or they return the elicited value directly instead of building an ElicitResult.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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