{"record":{"id":"6790a7a6ba1488d4","repo":"spring-projects/spring-ai","slug":"unsupported-list-item-type-itemclassname-expec","errorCode":null,"errorMessage":"Unsupported list item type: {itemClassName}. Expected String or ResourceContents.","messagePattern":"Unsupported list item type: (.+?)\\. Expected String or ResourceContents\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/DefaultMcpReadResourceResultConverter.java","lineNumber":176,"sourceCode":"\t\t\t// BlobResourceContents)\n\t\t\tList<String> stringList = (List<String>) list;\n\t\t\tList<ResourceContents> result = new ArrayList<>(stringList.size());\n\n\t\t\tif (contentType == ContentType.TEXT) {\n\t\t\t\tfor (String text : stringList) {\n\t\t\t\t\tresult.add(TextResourceContents.builder(requestUri, text).mimeType(mimeType).meta(meta).build());\n\t\t\t\t}\n\t\t\t}\n\t\t\telse { // BLOB\n\t\t\t\tfor (String blob : stringList) {\n\t\t\t\t\tresult.add(BlobResourceContents.builder(requestUri, blob).mimeType(mimeType).meta(meta).build());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\t\telse {\n\t\t\tthrow new IllegalArgumentException(\"Unsupported list item type: \" + firstItem.getClass().getName()\n\t\t\t\t\t+ \". Expected String or ResourceContents.\");\n\t\t}\n\t}\n\n\t/**\n\t * Converts a String result to a list of ResourceContents with metadata.\n\t * @param stringResult The string result\n\t * @param requestUri The original request URI\n\t * @param contentType The content type (TEXT or BLOB)\n\t * @param mimeType The MIME type\n\t * @param meta The resource-level metadata to propagate to content items\n\t * @return A list containing a single ResourceContents\n\t */\n\tprivate List<ResourceContents> convertStringResult(String stringResult, String requestUri, ContentType contentType,\n\t\t\tString mimeType, Map<String, Object> meta) {\n\t\tif (contentType == ContentType.TEXT) {\n\t\t\treturn List\n\t\t\t\t.of(TextResourceContents.builder(requestUri, stringResult).mimeType(mimeType).meta(meta).build());","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/DefaultMcpReadResourceResultConverter.java#L158-L194","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n@McpResource(uri = \"data://{id}\")\npublic List<MyDto> getData(String id) { return repo.findAll(); }\n\n// after\n@McpResource(uri = \"data://{id}\")\npublic ReadResourceResult getData(String id) {\n    List<ResourceContents> contents = repo.findAll().stream()\n        .map(dto -> ResourceContents.builder()\n            .uri(\"data://\" + id)\n            .mimeType(\"application/json\")\n            .text(toJson(dto))\n            .build())\n        .toList();\n    return new ReadResourceResult(contents);\n}","handlingStrategy":"validation","validationCode":"boolean isConvertibleList(Object result) {\n    if (!(result instanceof List<?> list) || list.isEmpty()) return true; // empty/None lists handled elsewhere\n    Object first = list.get(0);\n    return first instanceof String || first instanceof ResourceContents;\n}","typeGuard":"static boolean isSupportedListItem(Object item) {\n    return item instanceof String || item instanceof ResourceContents;\n}","tryCatchPattern":"try {\n    ReadResourceResult result = callback.apply(exchange, request);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Unsupported list item type\")) {\n        log.error(\"Resource method returned unsupported list element type; return List<String> or List<ResourceContents>\", e);\n    } else { throw e; }\n}","preventionTips":["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."],"tags":["mcp","resource-conversion","unsupported-type","illegal-argument"],"backgroundTag":"unsupported-operation","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}