spring-projects/spring-ai · error · IllegalArgumentException
Unsupported list item type: {itemClassName}. Expected String
Error message
Unsupported list item type: {itemClassName}. Expected String or ResourceContents. What it means
DefaultMcpReadResourceResultConverter.convertListResult throws this when a resource method returns a List whose first element is neither String nor ResourceContents. The converter only knows how to map those two element types into ResourceContents entries for the ReadResourceResult. Any other element type makes the conversion ambiguous, so it fails fast with IllegalArgumentException.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/DefaultMcpReadResourceResultConverter.java:176
// BlobResourceContents)
List<String> stringList = (List<String>) list;
List<ResourceContents> result = new ArrayList<>(stringList.size());
if (contentType == ContentType.TEXT) {
for (String text : stringList) {
result.add(TextResourceContents.builder(requestUri, text).mimeType(mimeType).meta(meta).build());
}
}
else { // BLOB
for (String blob : stringList) {
result.add(BlobResourceContents.builder(requestUri, blob).mimeType(mimeType).meta(meta).build());
}
}
return result;
}
else {
throw new IllegalArgumentException("Unsupported list item type: " + firstItem.getClass().getName()
+ ". Expected String or ResourceContents.");
}
}
/**
* Converts a String result to a list of ResourceContents with metadata.
* @param stringResult The string result
* @param requestUri The original request URI
* @param contentType The content type (TEXT or BLOB)
* @param mimeType The MIME type
* @param meta The resource-level metadata to propagate to content items
* @return A list containing a single ResourceContents
*/
private List<ResourceContents> convertStringResult(String stringResult, String requestUri, ContentType contentType,
String mimeType, Map<String, Object> meta) {
if (contentType == ContentType.TEXT) {
return List
.of(TextResourceContents.builder(requestUri, stringResult).mimeType(mimeType).meta(meta).build());View on GitHub (pinned to 98a7beda4f)
Solutions
- Change the method to return List<String> (e.g., JSON-serialized items via ObjectMapper) or List<ResourceContents> (build with ResourceContents.builder().uri(...).mimeType("application/json").text/jsonData(...) ).
- Return a ReadResourceResult directly and construct the ResourceContents list yourself for full control.
- If elements are heterogeneous, map each item explicitly: Strings pass through, objects should be converted to ResourceContents before returning.
Example fix
// before
@McpResource(uri = "data://{id}")
public List<MyDto> getData(String id) { return repo.findAll(); }
// after
@McpResource(uri = "data://{id}")
public ReadResourceResult getData(String id) {
List<ResourceContents> contents = repo.findAll().stream()
.map(dto -> ResourceContents.builder()
.uri("data://" + id)
.mimeType("application/json")
.text(toJson(dto))
.build())
.toList();
return new ReadResourceResult(contents);
} Defensive patterns
Strategy: validation
Validate before calling
boolean isConvertibleList(Object result) {
if (!(result instanceof List<?> list) || list.isEmpty()) return true; // empty/None lists handled elsewhere
Object first = list.get(0);
return first instanceof String || first instanceof ResourceContents;
} Type guard
static boolean isSupportedListItem(Object item) {
return item instanceof String || item instanceof ResourceContents;
} Try / catch
try {
ReadResourceResult result = callback.apply(exchange, request);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("Unsupported list item type")) {
log.error("Resource method returned unsupported list element type; return List<String> or List<ResourceContents>", e);
} else { throw e; }
} Prevention
- Only declare resource methods returning ReadResourceResult, ResourceContents, String, or List of String/ResourceContents.
- Add a unit test per resource method that invokes it and asserts conversion succeeds.
- Serialize domain objects to JSON strings before returning them from resource methods.
When it happens
Trigger: An @McpResource-annotated method declares a return type like List<MyDto>, List<Integer>, or List<Object> and returns a non-empty list whose first element is not a String or ResourceContents instance. The converter inspects firstItem.getClass().getName() and rejects it during convertToReadResourceResult.
Common situations: Developers return domain objects (DTOs, records, JSON nodes) from resource methods assuming auto-serialization to JSON, or return mixed-type lists. It surfaces at runtime on the first resource read, not at registration time, so it often appears only in integration tests or production traffic.
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
- Unsupported return type:
- Unsupported return type:
- Single parameter must be of type ElicitRequest:
- First parameter must be of type Double or double: {method.ge
- Second parameter must be of type String: {method.getName()}
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/6790a7a6ba1488d4.
Report an issue: GitHub.