{"record":{"id":"b672b24a2809ebf3","repo":"spring-projects/spring-ai","slug":"failed-to-decode-base64-data-storing-as-string","errorCode":null,"errorMessage":"Failed to decode Base64 data, storing as string","messagePattern":"Failed to decode Base64 data, storing as string","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":967,"sourceCode":"\t\t\t\t\tmediaList.add(mediaBuilder.build());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn mediaList;\n\t}\n\n\tprivate void parseMediaData(JsonObject mediaJson, Media.Builder mediaBuilder) {\n\t\tJsonElement dataElement = mediaJson.get(\"data\");\n\t\tif (dataElement.isJsonPrimitive() && dataElement.getAsJsonPrimitive().isString()) {\n\t\t\tString dataString = dataElement.getAsString();\n\n\t\t\tif (mediaJson.has(\"dataType\") && \"base64\".equals(mediaJson.get(\"dataType\").getAsString())) {\n\t\t\t\ttry {\n\t\t\t\t\tbyte[] decodedBytes = Base64.getDecoder().decode(dataString);\n\t\t\t\t\tmediaBuilder.data(decodedBytes);\n\t\t\t\t}\n\t\t\t\tcatch (IllegalArgumentException e) {\n\t\t\t\t\tlogger.warn(\"Failed to decode Base64 data, storing as string\", e);\n\t\t\t\t\tmediaBuilder.data(dataString);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttry {\n\t\t\t\t\tmediaBuilder.data(URI.create(dataString));\n\t\t\t\t}\n\t\t\t\tcatch (IllegalArgumentException e) {\n\t\t\t\t\tmediaBuilder.data(dataString);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (dataElement.isJsonArray()) {\n\t\t\tJsonArray dataArray = dataElement.getAsJsonArray();\n\t\t\tbyte[] byteArray = new byte[dataArray.size()];\n\t\t\tfor (int i = 0; i < dataArray.size(); i++) {\n\t\t\t\tbyteArray[i] = dataArray.get(i).getAsByte();\n\t\t\t}","sourceCodeStart":949,"sourceCodeEnd":985,"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#L949-L985","documentation":"When reconstructing Media from stored JSON, if dataType is \"base64\" the repository decodes the data string with Base64.getDecoder(); on IllegalArgumentException it logs this warning and stores the raw string as the media data instead of the decoded bytes. The message/media is still built, but downstream code expecting byte[] may misbehave.","triggerScenarios":"A stored media entry whose dataString is declared base64 but is not valid Base64 (truncated, contains whitespace/URL characters, written by a different encoder variant such as MIME or URL-safe Base64).","commonSituations":"Data written with Base64.getMimeDecoder() or getUrlDecoder() then read here with the basic decoder; manually pasted/edited Redis data; truncated payloads from an earlier failed write; strings that are actually URIs mis-tagged as base64.","solutions":["Inspect the stored media JSON and verify the data really is valid Base64","If the writer used MIME/URL-safe Base64, re-encode it with the basic encoder or adjust dataType accordingly","Re-store the media with the original byte[] so Spring AI encodes it itself","Fix the dataType field (e.g. set it to a URI type) if the string is actually a URL, not base64","Handle the resulting Media defensively — its data may be a String, not byte[]"],"exampleFix":"// before\n{\"dataType\":\"base64\",\"data\":\"not valid base64!!\"}\n// after\nString fixed = Base64.getEncoder().encodeToString(rawBytes);\n{\"dataType\":\"base64\",\"data\":\"<validBase64>\"}","handlingStrategy":"validation","validationCode":"boolean isBase64(String s) {\n    return s != null && !s.isEmpty() && s.matches(\"^[A-Za-z0-9+/]+={0,2}$\") && s.length() % 4 == 0;\n}\n// run before marking dataType=base64 when persisting media","typeGuard":"static boolean isValidBase64(String s) {\n    if (s == null || s.isEmpty()) return false;\n    try { java.util.Base64.getDecoder().decode(s); return true; } catch (IllegalArgumentException e) { return false; }\n}","tryCatchPattern":"try { byte[] data = Base64.getDecoder().decode(mediaData); }\ncatch (IllegalArgumentException e) {\n    // data may actually be MIME/URL-safe base64 or a URI string — inspect and repair, don't blind-store\n    throw new IllegalStateException(\"Stored media marked base64 but not decodable\", e);\n}","preventionTips":["Use the same Base64 variant (basic) on write and read, or use the repository API to persist media","Validate base64 strings before persisting them with dataType=base64","Never manually edit media payloads in Redis","Detect truncation: check expected length/checksum of media data after reads"],"tags":["redis","base64","deserialization","media"],"backgroundTag":"invalid-argument-format","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"}