alibaba/spring-ai-alibaba · error · Exception

Unable to load checkpoint

Error message

Unable to load checkpoint

What it means

PostgresSaver.selectCheckpointById wraps SQLException, IOException, and ClassNotFoundException from fetching a specific checkpoint by threadId and checkpointId into Exception('Unable to load checkpoint'). It indicates the SELECT or row deserialization via readCheckpoint failed for that specific record.

Source

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

	}

	@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();
			}
		}
		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);
			insertCheckpoint(conn, threadId, checkpoint);
			conn.commit();
			log.debug("Checkpoint {} for thread {} inserted successfully.", checkpoint.getId(), threadId);
		}
		catch (SQLException | IOException ex) {
			log.error("Error inserting checkpoint with id {} in thread {}", checkpoint.getId(), threadId, ex);
			rollback(conn, checkpoint, threadId);
			throw new Exception("Unable to insert checkpoint", ex);
		}
	}

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Read the cause: SQLException vs ClassNotFoundException vs IOException to target the fix.
  2. Match the StateSerializer to the one used when the checkpoint was written.
  3. Keep backward-compatible state classes (same package/name, serialVersionUID) for old checkpoints.
  4. If the record is corrupt, remove it or recreate the checkpoint.

Example fix

// before
// state class moved package -> ClassNotFoundException
package com.newpkg; public class MyState implements Serializable {...}
// after
package com.oldpkg; public class MyState implements Serializable {...} // preserve original FQCN
Defensive patterns

Strategy: try-catch

Validate before calling

// Java: validate ids are well-formed before lookup
UUID.fromString(checkpointId); // throws IllegalArgumentException early if malformed

Try / catch

try {
    var cp = saver.get(config, checkpointId);
} catch (Exception e) {
    if ("Unable to load checkpoint".equals(e.getMessage()) && e.getCause() instanceof ClassNotFoundException cnfe) {
        // restore old state class or migrate data
    }
}

Prevention

When it happens

Trigger: Loading a checkpoint by explicit id when the query fails (connectivity, schema mismatch) or the row's serialized state cannot be decoded (wrong serializer/content type, ClassNotFoundException, corrupted payload).

Common situations: Time-travel/replay to a specific checkpoint id created by an older app version with different serialization; renamed state classes causing ClassNotFoundException; DB connectivity hiccups.

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