alibaba/spring-ai-alibaba · error · Exception

Unable to load checkpoints

Error message

Unable to load checkpoints

What it means

H2Saver.selectCheckpoints reads all checkpoints for a thread from the H2 database. Any SQLException (or deserialization IOException/ClassNotFoundException from readCheckpoint) is wrapped in a checked Exception with message 'Unable to load checkpoints', preserving the cause.

Source

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

				.nextNodeId(resultSet.getString(3))
				.state(decodeState(resultSet.getString(4), resultSet.getString(5)))
				.build();
	}

	@Override
	protected LinkedList<Checkpoint> selectCheckpoints(String threadId) throws Exception {
		LinkedList<Checkpoint> checkpoints = new LinkedList<>();
		try (Connection conn = getConnection();
				PreparedStatement ps = conn.prepareStatement(SELECT_CHECKPOINTS)) {
			ps.setString(1, threadId);
			try (ResultSet rs = ps.executeQuery()) {
				while (rs.next()) {
					checkpoints.add(readCheckpoint(rs));
				}
			}
		}
		catch (SQLException | IOException | ClassNotFoundException ex) {
			throw new Exception("Unable to load checkpoints", ex);
		}
		return checkpoints;
	}

	@Override
	protected Optional<Checkpoint> selectLatestCheckpoint(String threadId) throws Exception {
		try (Connection conn = getConnection();
				PreparedStatement ps = conn.prepareStatement(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);

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Inspect the cause chain (getCause()) for the underlying SQLException/IOException
  2. Verify the H2 database file exists, is not locked by another process, and the table schema matches
  3. Restore from backup or start with a fresh checkpoint database if corrupt
  4. Ensure the same serializer and classpath versions used for writing are present when reading

Example fix

// before
try { saver.release(conn); } catch (Exception e) {}
// after
try { saver.release(conn); } catch (Exception e) {
    log.error("checkpoint load failed", e.getCause());
    throw new UncheckedIOException(new IOException(e.getCause()));
}
Defensive patterns

Strategy: try-catch

Validate before calling

try (Connection c = getConnection(); ResultSet rs = c.createStatement().executeQuery("SELECT 1 FROM checkpoints LIMIT 1")) { /* table reachable */ }

Try / catch

try { checkpoints = saver.getAll(threadId); } catch (Exception e) { Throwable root = e.getCause(); log.error("H2 read failed", root); }

Prevention

When it happens

Trigger: Calling the saver's load/list checkpoint path when the H2 table is missing, the DB file is locked/corrupt, connection fails, or stored rows cannot be deserialized.

Common situations: H2 database file deleted or corrupted, schema mismatch after upgrade, concurrent access to the same H2 file from two processes, classpath changes breaking deserialization of stored classes.

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