alibaba/spring-ai-alibaba · error · Exception

Unable to load latest checkpoint

Error message

Unable to load latest checkpoint

What it means

PostgresSaver.selectLatestCheckpoint wraps SQLException, IOException, and ClassNotFoundException from reading the most recent checkpoint of a thread into Exception('Unable to load latest checkpoint'). It fires when the latest-checkpoint SELECT or the row deserialization via readCheckpoint fails.

Source

Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/postgresql/PostgresSaver.java:428

		return checkpoints;
	}

	@Override
	protected Optional<Checkpoint> selectLatestCheckpoint(String threadId) throws Exception {
		try (Connection conn = getConnection();
				PreparedStatement ps = conn.prepareStatement(SELECT_LATEST_CHECKPOINT)) {

			log.trace("Executing select latest checkpoint:\n---\n{}---", SELECT_LATEST_CHECKPOINT);
			ps.setString(1, threadId);
			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 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)) {

			log.trace("Executing select checkpoint by id:\n---\n{}---", SELECT_CHECKPOINT_BY_ID);
			ps.setString(1, threadId);
			ps.setObject(2, UUID.fromString(checkpointId), Types.OTHER);
			try (ResultSet rs = ps.executeQuery()) {
				if (rs.next()) {
					return Optional.of(readCheckpoint(rs));
				}
				return Optional.empty();
			}
		}

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Check the wrapped cause to distinguish SQL failure vs deserialization failure.
  2. Ensure the StateSerializer matches the one that wrote the rows (content type must match).
  3. Restore availability of any state classes referenced by the payload (avoid renaming/moving serialized classes).
  4. If the row is unrecoverable, delete the corrupt checkpoint row so the thread can restart from an earlier/empty checkpoint.

Example fix

// before
// latest row written with PLAIN_TEXT serializer, app now uses binary
saver.get(...).resume(thread); // throws
// after
// either revert serializer or purge incompatible rows:
// DELETE FROM checkpoints WHERE thread_id = ?;
saver.get(...).resume(thread);
Defensive patterns

Strategy: fallback

Validate before calling

// Java: confirm the thread has readable checkpoints first
boolean has = saver.getTuple(new RepositorySearch(threadId)).isPresent();

Try / catch

try {
    return saver.get(config);
} catch (Exception e) {
    if ("Unable to load latest checkpoint".equals(e.getMessage())) {
        // fall back to creating a fresh checkpoint instead of failing resume
        return Optional.empty();
    }
    throw e;
}

Prevention

When it happens

Trigger: Resuming a thread from its latest checkpoint when the query fails (connection issues, missing table) or the stored payload cannot be decoded (serializer mismatch, ClassNotFoundException for state classes, corrupted blob).

Common situations: Restarting an app after a serializer change against existing rows; missing class after refactor of state objects; Postgres restart/connection drop; schema drift after version upgrade.

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/eddea4281cfd11b8. Report an issue: GitHub.