alibaba/spring-ai-alibaba · error · Exception

Unable to load checkpoint

Error message

Unable to load checkpoint

What it means

H2Saver.selectCheckpointById wraps any SQLException, IOException (deserialization), or ClassNotFoundException thrown while loading a single checkpoint row by id into a generic Exception with message 'Unable to load checkpoint'. It signals that the SELECT or the subsequent deserialization of the stored checkpoint state failed, not that the checkpoint is absent (that returns Optional.empty()).

Source

Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/h2/H2Saver.java:356

			throw new Exception("Unable to load latest checkpoint", ex);
		}
	}

	@Override
	protected Optional<Checkpoint> selectCheckpointById(String threadId, String checkpointId) throws Exception {
		try (Connection conn = getConnection();
				PreparedStatement ps = conn.prepareStatement(SELECT_CHECKPOINT_BY_ID)) {
			ps.setString(1, threadId);
			ps.setString(2, checkpointId);
			try (ResultSet rs = ps.executeQuery()) {
				if (rs.next()) {
					return Optional.of(readCheckpoint(rs));
				}
				return Optional.empty();
			}
		}
		catch (SQLException | IOException | ClassNotFoundException ex) {
			throw new Exception("Unable to load checkpoint", ex);
		}
	}

	@Override
	protected void insertCheckpoint(String threadId, Checkpoint checkpoint) throws Exception {
		Connection conn = null;
		try (Connection ignored = conn = getConnection()) {
			conn.setAutoCommit(false);
			String persistedThreadId = activeThreadId(conn, threadId);
			try (PreparedStatement ps = conn.prepareStatement(INSERT_CHECKPOINT)) {
				ps.setString(1, checkpoint.getId());
				ps.setString(2, persistedThreadId);
				ps.setString(3, checkpoint.getNodeId());
				ps.setString(4, checkpoint.getNextNodeId());
				ps.setString(5, encodeState(checkpoint.getState()));
				ps.setString(6, stateSerializer.contentType());
				ps.executeUpdate();
			}

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Inspect the wrapped cause (getCause()) to distinguish SQL failure vs ClassNotFound vs IO/deserialization failure.
  2. Verify the checkpoints table exists and matches the current H2Saver schema (re-run the saver's table DDL or init).
  3. Ensure the state classes stored in checkpoints are on the runtime classpath and serializable by the configured stateSerializer.
  4. If the checkpoint was written by a different library version, discard old checkpoint data or migrate it with the same serializer version.

Example fix

// before: serializer changed between versions, old blobs unreadable
H2Saver.builder().stateSerializer(new JacksonStateSerializer(...)).build();
// after: pin the same serializer/format used to write the checkpoints, and check the cause
try {
  saver.get(threadId, checkpointId);
} catch (Exception e) {
  log.error("checkpoint load failed", e.getCause()); // see real reason
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check: schema + cause triage
if (!tableExists(dataSource, "checkpoints")) throw new IllegalStateException("checkpoints table missing");

Type guard

boolean loadable(Exception e) { return !(e.getCause() instanceof ClassNotFoundException); }

Try / catch

try { return saver.get(threadId, checkpointId); }
catch (Exception e) {
  if (e.getCause() instanceof ClassNotFoundException cnf) throw new IllegalStateException("state class missing on classpath", cnf);
  throw new RuntimeException("checkpoint load failed", e.getCause());
}

Prevention

When it happens

Trigger: Calling a checkpoint-loading API (e.g. BaseCheckpointSaver.get/getById backed by H2Saver.selectCheckpointById) when: the H2 query throws (table missing/corrupt, connection failure), the serialized state column is unreadable by the configured stateSerializer (content type changed), or the checkpoint's state class is not on the classpath at read time (ClassNotFoundException).

Common situations: Reading checkpoints written by an older library version whose serializer format changed; H2 database file deleted or corrupted; running the app without the classes that implement the graph state (different fat jar); H2 server restarted or connection pool exhausted.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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