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

  1. Configure the same StateSerializer that was used when the checkpoints were written.
  2. If the serializer must change, clear/migrate the checkpoint table (old rows cannot be decoded with the new content type).
  3. Check the stored content_type column value and match it against stateSerializer.contentType() to identify which serializer is expected.
  4. 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

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


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/04b29462eb773970. Report an issue: GitHub.