spring-projects/spring-ai · error · IllegalArgumentException

List items must be of type String

Error message

List items must be of type String

What it means

This library converts the annotated @McpComplete method's return value into an MCP CompleteResult. When the return value is a List, every element must be a String, because MCP completion values are strings. If any element is not a String, the AsyncStatelessMcpCompleteMethodCallback throws this IllegalArgumentException instead of emitting invalid completion data.

Source

Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/complete/AsyncStatelessMcpCompleteMethodCallback.java:124

		if (result instanceof CompleteResult) {
			return (CompleteResult) result;
		}

		if (result instanceof CompleteCompletion) {
			return new CompleteResult((CompleteCompletion) result);
		}

		if (result instanceof List) {
			List<?> list = (List<?>) result;
			List<String> values = new ArrayList<>();

			for (Object item : list) {
				if (item instanceof String) {
					values.add((String) item);
				}
				else {
					throw new IllegalArgumentException("List items must be of type String");
				}
			}

			return new CompleteResult(new CompleteCompletion(values, values.size(), false));
		}

		if (result instanceof String) {
			return new CompleteResult(new CompleteCompletion(List.of((String) result), 1, false));
		}

		throw new IllegalArgumentException("Unsupported return type: " + result.getClass().getName());
	}

	/**
	 * Validates that the method return type is compatible with the complete callback.
	 * @param method The method to validate
	 * @throws IllegalArgumentException if the return type is not compatible
	 */

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Map the returned list elements to strings before returning: list.stream().map(String::valueOf).collect(Collectors.toList())
  2. Ensure the annotated method is declared to return List<String> or Mono<List<String>> so type errors surface at compile time
  3. Call toString()/name()/mapping on enum or object elements before returning
  4. Catch IllegalArgumentException in a wrapper if dynamic data cannot be guaranteed

Example fix

// before
return List.of(42, 99); // List<Integer>
// after
return List.of("42", "99"); // List<String>
Defensive patterns

Strategy: validation

Validate before calling

Object result = myMethod();
if (result instanceof List<?> list && list.stream().allMatch(String.class::isInstance)) {
    return callback.apply(exchange, request); // safe
}

Type guard

static boolean isStringList(Object o) {
    return o instanceof List<?> l && l.stream().allMatch(String.class::isInstance);
}

Try / catch

try {
    return callback.apply(exchange, request);
} catch (IllegalArgumentException e) {
    log.error("Completion result contained non-string items", e);
    return new CompleteResult(new CompleteCompletion(List.of(), 0, false));
}

Prevention

When it happens

Trigger: An @McpComplete method returns (or wraps in Mono) a List containing non-String elements (e.g. List<Integer>, List<MyEnum>, List<Object> built from user data), which convertToCompleteResult iterates via the instanceof String check.

Common situations: Returning enums, numeric IDs, or domain objects directly instead of mapping them to strings; untyped raw Lists from legacy code; building suggestions from a database column that returns non-string types.

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