{"record":{"id":"42147313b5acfc69","repo":"spring-projects/spring-ai","slug":"unknown-message-type-type-returning-generic","errorCode":null,"errorMessage":"Unknown message type: + type + , returning generic UserMessage","messagePattern":"Unknown message type: \\+ type \\+ , returning generic UserMessage","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"memory-repositories/spring-ai-model-chat-memory-repository-redis/src/main/java/org/springframework/ai/chat/memory/repository/redis/RedisChatMemoryRepository.java","lineNumber":920,"sourceCode":"\t\t\t\tJsonArray responseArray = json.getAsJsonArray(\"toolResponses\");\n\t\t\t\tfor (JsonElement responseElement : responseArray) {\n\t\t\t\t\tJsonObject responseJson = responseElement.getAsJsonObject();\n\n\t\t\t\t\tString id = responseJson.has(\"id\") ? responseJson.get(\"id\").getAsString() : \"\";\n\t\t\t\t\tString name = responseJson.has(\"name\") ? responseJson.get(\"name\").getAsString() : \"\";\n\t\t\t\t\tString responseData = responseJson.has(\"responseData\")\n\t\t\t\t\t\t\t? responseJson.get(\"responseData\").getAsString() : \"\";\n\n\t\t\t\t\ttoolResponses.add(new ToolResponseMessage.ToolResponse(id, name, responseData));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn ToolResponseMessage.builder().responses(toolResponses).metadata(metadata).build();\n\t\t}\n\n\t\t// For unknown message types, return a generic UserMessage\n\t\tif (logger.isWarnEnabled()) {\n\t\t\tlogger.warn(\"Unknown message type: \" + type + \", returning generic UserMessage\");\n\t\t}\n\t\treturn UserMessage.builder().text(content).metadata(metadata).build();\n\t}\n\n\tprivate List<Media> parseMedia(JsonObject json) {\n\t\tList<Media> mediaList = new ArrayList<>();\n\t\tif (json.has(\"media\") && json.get(\"media\").isJsonArray()) {\n\t\t\tJsonArray mediaArray = json.getAsJsonArray(\"media\");\n\t\t\tfor (JsonElement mediaElement : mediaArray) {\n\t\t\t\tJsonObject mediaJson = mediaElement.getAsJsonObject();\n\t\t\t\tString mimeTypeString = mediaJson.has(\"mimeType\") ? mediaJson.get(\"mimeType\").getAsString() : null;\n\n\t\t\t\tif (mimeTypeString != null) {\n\t\t\t\t\tMimeType mimeType = MimeType.valueOf(mimeTypeString);\n\t\t\t\t\tMedia.Builder mediaBuilder = Media.builder().mimeType(mimeType);\n\n\t\t\t\t\tif (mediaJson.has(\"id\")) {\n\t\t\t\t\t\tmediaBuilder.id(mediaJson.get(\"id\").getAsString());","sourceCodeStart":902,"sourceCodeEnd":938,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/memory-repositories/spring-ai-model-chat-memory-repository-redis/src/main/java/org/springframework/ai/chat/memory/repository/redis/RedisChatMemoryRepository.java#L902-L938","documentation":"In the message deserialization helper of RedisChatMemoryRepository, when a stored message's 'type' does not match any known kind, this warning is logged and the code degrades gracefully by returning a generic UserMessage built from the stored content and metadata. Unlike the get() path, the message is not dropped — but its original role (assistant/tool/system) is lost.","triggerScenarios":"Stored Redis message JSON with a 'type' value the repository doesn't recognize, hit while rebuilding a single message (e.g. from last()/findByConversationId paths).","commonSituations":"Cross-version Spring AI data in Redis; externally written or hand-edited entries; custom message types persisted without a matching reader; corrupted type strings (case differences, whitespace).","solutions":["Check the stored entry's 'type' value in Redis and correct it to a supported kind (user/assistant/system/tool)","Keep the application's Spring AI version consistent with the version that wrote the data","Migrate unknown-typed messages to the closest supported type before reading","Validate JSON written by any custom code sharing the keyspace","Accept the fallback only if losing the original role is harmless for your use case"],"exampleFix":"// before\n// stored: {\"type\":\"ToolResponseMessage\", ...}  -> returned as generic UserMessage\n// after\n// stored: {\"type\":\"tool\", ...}  -> correctly rebuilt as ToolResponseMessage","handlingStrategy":"validation","validationCode":"String type = json.get(\"type\").getAsString();\nif (!Set.of(\"user\",\"assistant\",\"system\",\"tool\").contains(type)) {\n    throw new IllegalStateException(\"Unreadable stored message type: \" + type);\n}","typeGuard":"static boolean isSupportedMessageType(String type) {\n    return type != null && switch (type.toLowerCase()) { case \"user\",\"assistant\",\"system\",\"tool\" -> true; default -> false; };\n}","tryCatchPattern":"Message m = repositoryMessage(obj);\nif (m instanceof UserMessage um && expectedAssistant) { log.warn(\"message degraded to UserMessage; check stored type\"); }","preventionTips":["Ensure the writing side persists canonical lowercase type tags","Migrate legacy entries after Spring AI upgrades before reading them","Add a startup sanity check that samples stored messages and verifies their types","Treat this warning as data-quality signal, not just noise"],"tags":["redis","deserialization","message-type","fallback"],"backgroundTag":"unexpected-response-shape","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"}