alibaba/spring-ai-alibaba · error · NoSuchElementException

Checkpoint with id %s not found!

Error message

Checkpoint with id %s not found!

What it means

updateCheckpoint throws NoSuchElementException('Checkpoint with id %s not found!') when the UPDATE statement affects 0 rows, meaning no checkpoint row matched the given checkpointId and threadId. The connection is rolled back before throwing; this is a logical not-found, distinct from the wrapped 'Unable to update checkpoint' SQL failure.

Source

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

	}

	@Override
	protected void updateCheckpoint(String threadId, String checkpointId, Checkpoint checkpoint) throws Exception {
		Connection conn = null;
		try (Connection ignored = conn = getConnection()) {
			conn.setAutoCommit(false);
			try (PreparedStatement ps = conn.prepareStatement(UPDATE_CHECKPOINT)) {
				ps.setString(1, checkpoint.getId());
				ps.setString(2, checkpoint.getNodeId());
				ps.setString(3, checkpoint.getNextNodeId());
				ps.setString(4, encodeState(checkpoint.getState()));
				ps.setString(5, stateSerializer.contentType());
				ps.setString(6, checkpointId);
				ps.setString(7, threadId);
				int rowsAffected = ps.executeUpdate();
				if (rowsAffected == 0) {
					conn.rollback();
					throw new NoSuchElementException(format("Checkpoint with id %s not found!", checkpointId));
				}
			}
			conn.commit();
		}
		catch (SQLException | IOException ex) {
			rollback(conn, threadId);
			throw new Exception("Unable to update checkpoint", ex);
		}
	}

	@Override
	protected void deleteCheckpoints(String threadId, Collection<String> checkpointIds) throws Exception {
		if (checkpointIds.isEmpty()) {
			return;
		}
		String placeholders = String.join(", ", Collections.nCopies(checkpointIds.size(), "?"));
		try (Connection conn = getConnection();
				PreparedStatement ps = conn.prepareStatement(DELETE_CHECKPOINTS.formatted(placeholders))) {

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Verify the checkpointId exists in the target thread (query the checkpoints table or list them via the saver API).
  2. Ensure threadId and checkpointId are from the same thread/run.
  3. If the checkpoint may legitimately be absent, use insert instead of update, or check existence before updating.
  4. Check whether retention/deletion logic removed the checkpoint before the update.

Example fix

// before: blind update with stale id
saver.update(threadId, checkpointId, checkpoint); // NoSuchElementException
// after: verify existence first
if (saver.list(threadId).contains(checkpointId)) {
  saver.update(threadId, checkpointId, checkpoint);
}
Defensive patterns

Strategy: validation

Validate before calling

boolean exists = saver.list(threadId).contains(checkpointId); if (!exists) throw new IllegalStateException("checkpoint " + checkpointId + " absent in thread " + threadId);

Try / catch

try { saver.update(threadId, checkpointId, checkpoint); }
catch (NoSuchElementException e) { log.warn("checkpoint vanished, re-inserting"); saver.addCheckpoint(threadId, checkpoint); }

Prevention

When it happens

Trigger: Calling an update/replace-checkpoint API with a checkpointId that was never inserted, was already deleted, or belongs to a different threadId — the WHERE clause matches nothing.

Common situations: Reusing a stale checkpoint id after the thread's checkpoints were pruned; passing an id from another thread; a race where another process deleted the checkpoint between read and update.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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