alibaba/spring-ai-alibaba · error · IllegalStateException
Content Type used for store state '%s' is different from one
Error message
Content Type used for store state '%s' is different from one '%s' used for deserialize it
What it means
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.
Source
Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/postgresql/PostgresSaver.java:331
conn.rollback();
log.warn("Transaction rolled back for thread {}", threadId);
}
catch (SQLException exRollback) {
log.error("Failed to rollback transaction for thread {}", threadId, exRollback);
}
}
private String encodeState(Map<String, Object> data) throws IOException {
var binaryData = stateSerializer.dataToBytes(data);
var base64Data = Base64.getEncoder().encodeToString(binaryData);
return format("""
{"binaryPayload": "%s"}
""", base64Data);
}
private Map<String, Object> decodeState(byte[] binaryPayload, String contentType) throws IOException, ClassNotFoundException {
if (!Objects.equals(contentType, stateSerializer.contentType())) {
throw new IllegalStateException(
format("Content Type used for store state '%s' is different from one '%s' used for deserialize it",
contentType,
stateSerializer.contentType()));
}
byte[] bytes = Base64.getDecoder().decode(binaryPayload);
return stateSerializer.dataFromBytes(bytes);
}
protected void initTable(CreateOption createOption) throws SQLException {
String sqlCommand = null;
try (Connection connection = getConnection(); Statement statement = connection.createStatement()) {
if (createOption == CreateOption.CREATE_OR_REPLACE) {
log.trace("Executing drop tables:\n---\n{}---", DROP_TABLES);
sqlCommand = DROP_TABLES;
statement.executeUpdate(sqlCommand);
}
if (createOption == CreateOption.CREATE_OR_REPLACE ||View on GitHub (pinned to f82da0b50f)
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.
Example fix
// before
PostgresSaver.builder()
.dataSource(ds)
.stateSerializer(new JacksonStateSerializer(...)) // DB rows written with JDK serializer
.build();
// after
PostgresSaver.builder()
.dataSource(ds)
.stateSerializer(new JDKStateSerializer(...)) // match the writer's contentType
.build(); Defensive patterns
Strategy: validation
Validate before calling
// Java: verify serializer consistency before reading checkpoints
String storedContentType = rs.getString("content_type");
if (!Objects.equals(storedContentType, stateSerializer.contentType())) {
// abort or migrate instead of failing deep inside decodeState
throw new ConfigurationException("Checkpoint DB written with content type " + storedContentType
+ " but configured serializer uses " + stateSerializer.contentType());
} Try / catch
try {
var checkpoints = saver.list(config);
} catch (IllegalStateException e) {
if (e.getMessage().startsWith("Content Type used for store state")) {
// switch serializer or clear/migrate the checkpoint table
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Content Type used for store state '%s' is different from one
- Unable to insert checkpoint
- Unable to update checkpoint
- Unable to load checkpoint
- Unable to insert checkpoint
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/04b29462eb773970.
Report an issue: GitHub.