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

  1. 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(...) ).
  2. Return a ReadResourceResult directly and construct the ResourceContents list yourself for full control.
  3. 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

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


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