{"record":{"id":"04b29462eb773970","repo":"alibaba/spring-ai-alibaba","slug":"content-type-used-for-store-state-s-is-differen-04b294","errorCode":null,"errorMessage":"Content Type used for store state '%s' is different from one '%s' used for deserialize it","messagePattern":"Content Type used for store state '(.+?)' is different from one '(.+?)' used for deserialize it","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/postgresql/PostgresSaver.java","lineNumber":331,"sourceCode":"\t\t\tconn.rollback();\n\t\t\tlog.warn(\"Transaction rolled back for thread {}\", threadId);\n\t\t}\n\t\tcatch (SQLException exRollback) {\n\t\t\tlog.error(\"Failed to rollback transaction for thread {}\", threadId, exRollback);\n\t\t}\n\t}\n\n\tprivate String encodeState(Map<String, Object> data) throws IOException {\n\t\tvar binaryData = stateSerializer.dataToBytes(data);\n\t\tvar base64Data = Base64.getEncoder().encodeToString(binaryData);\n\t\treturn format(\"\"\"\n\t\t\t\t{\"binaryPayload\": \"%s\"}\n\t\t\t\t\"\"\", base64Data);\n\t}\n\n\tprivate Map<String, Object> decodeState(byte[] binaryPayload, String contentType) throws IOException, ClassNotFoundException {\n\t\tif (!Objects.equals(contentType, stateSerializer.contentType())) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\t\tformat(\"Content Type used for store state '%s' is different from one '%s' used for deserialize it\",\n\t\t\t\t\t\t\tcontentType,\n\t\t\t\t\t\t\tstateSerializer.contentType()));\n\t\t}\n\n\t\tbyte[] bytes = Base64.getDecoder().decode(binaryPayload);\n\t\treturn stateSerializer.dataFromBytes(bytes);\n\t}\n\n\tprotected void initTable(CreateOption createOption) throws SQLException {\n\t\tString sqlCommand = null;\n\t\ttry (Connection connection = getConnection(); Statement statement = connection.createStatement()) {\n\t\t\tif (createOption == CreateOption.CREATE_OR_REPLACE) {\n\t\t\t\tlog.trace(\"Executing drop tables:\\n---\\n{}---\", DROP_TABLES);\n\t\t\t\tsqlCommand = DROP_TABLES;\n\t\t\t\tstatement.executeUpdate(sqlCommand);\n\t\t\t}\n\t\t\tif (createOption == CreateOption.CREATE_OR_REPLACE ||","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/postgresql/PostgresSaver.java#L313-L349","documentation":"PostgresSaver.decodeState checks that the content type stored with a checkpoint row matches the contentType() of the configured StateSerializer before deserializing state. On mismatch it throws IllegalStateException, preventing silent corruption from decoding binary payload bytes with the wrong serialization format. Typically this means checkpoints were written with a different serializer configuration than the one used to read them.","triggerScenarios":"Reading checkpoints from a database previously populated by an app that used a different StateSerializer (e.g. Jackson vs JDK-serialization-based, PLAIN_TEXT vs BINARY content type) while the blob column still holds the old contentType string; switching serializer versions or classes between deployments.","commonSituations":"Upgrading the application and changing stateSerializer on PostgresSaver.builder() against an existing checkpoint table; running two app versions against the same DB; copying checkpoint data between environments configured with different serializers.","solutions":["Configure the same StateSerializer that was used when the checkpoints were written.","If the serializer must change, clear/migrate the checkpoint table (old rows cannot be decoded with the new content type).","Check the stored content_type column value and match it against stateSerializer.contentType() to identify which serializer is expected.","Pin the serializer configuration in application config so all instances and versions use the identical setting."],"exampleFix":"// before\nPostgresSaver.builder()\n    .dataSource(ds)\n    .stateSerializer(new JacksonStateSerializer(...)) // DB rows written with JDK serializer\n    .build();\n// after\nPostgresSaver.builder()\n    .dataSource(ds)\n    .stateSerializer(new JDKStateSerializer(...)) // match the writer's contentType\n    .build();","handlingStrategy":"validation","validationCode":"// Java: verify serializer consistency before reading checkpoints\nString storedContentType = rs.getString(\"content_type\");\nif (!Objects.equals(storedContentType, stateSerializer.contentType())) {\n    // abort or migrate instead of failing deep inside decodeState\n    throw new ConfigurationException(\"Checkpoint DB written with content type \" + storedContentType\n        + \" but configured serializer uses \" + stateSerializer.contentType());\n}","typeGuard":null,"tryCatchPattern":"try {\n    var checkpoints = saver.list(config);\n} catch (IllegalStateException e) {\n    if (e.getMessage().startsWith(\"Content Type used for store state\")) {\n        // switch serializer or clear/migrate the checkpoint table\n    }\n}","preventionTips":["Pin the StateSerializer implementation in configuration; never change it without a data migration.","Record content_type per row and inspect it after upgrades.","Keep the same serializer across all app instances sharing a checkpoint DB.","Test serializer changes against a copy of the production checkpoint table."],"tags":["postgres","serialization","content-type","checkpoint","version-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}