alibaba/spring-ai-alibaba · error · Exception

Unable to update checkpoint

Error message

Unable to update checkpoint

What it means

updateCheckpoint wraps any SQLException or IOException raised during the update (or rollback) in a generic Exception with message 'Unable to update checkpoint', after logging and rolling back the transaction. The concrete database or serialization error remains available as the cause.

Source

Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/oracle/OracleSaver.java:507

				preparedStatement.setString(3, checkpoint.getNextNodeId());
				preparedStatement.setObject(4, encodedState, OracleType.JSON);
				preparedStatement.setString(5, stateSerializer.contentType());
				preparedStatement.setString(6, checkpointId);
				preparedStatement.setString(7, threadName);
				int rowsAffected = preparedStatement.executeUpdate();
				if (rowsAffected == 0) {
					conn.rollback();
					throw new NoSuchElementException(format("Checkpoint with id %s not found!", checkpointId));
				}
			}

			conn.commit();
			log.debug("Checkpoint with id {} for thread {} updated successfully.", checkpoint.getId(), threadName);
		}
		catch (SQLException | IOException ex) {
			log.error("Error updating checkpoint with id {} in thread {}", checkpoint.getId(), threadName, ex);
			rollback(conn, checkpoint, threadName);
			throw new Exception("Unable to update checkpoint", ex);
		}
	}

	@Override
	protected void deleteCheckpoints(String threadName, Collection<String> checkpointIds) throws Exception {
		if (checkpointIds.isEmpty()) {
			return;
		}
		try (Connection connection = dataSource.getConnection();
				PreparedStatement preparedStatement = connection.prepareStatement(
						DELETE_CHECKPOINTS.formatted(String.join(", ", Collections.nCopies(checkpointIds.size(), "?"))))) {
			int index = 1;
			for (String checkpointId : checkpointIds) {
				preparedStatement.setString(index++, checkpointId);
			}
			preparedStatement.setString(index, threadName);
			preparedStatement.executeUpdate();
		}

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Inspect the wrapped cause and the logged error for the exact failure
  2. Retry the update with backoff if it was a transient connection/lock timeout
  3. Serialize state to a supported size/type or increase Oracle limits (e.g. LOB sizing) if payload is the issue
  4. Verify the checkpoint table schema still matches what the saver expects after migrations
Defensive patterns

Strategy: retry

Validate before calling

// ensure no concurrent writer and DB reachable before update
try (Connection c = dataSource.getConnection();
     PreparedStatement ps = c.prepareStatement("SELECT 1 FROM CKP_CHECKPOINTS WHERE THREAD_NAME=? AND CHECKPOINT_ID=?")) {
    ps.setString(1, threadName); ps.setString(2, checkpointId);
    try (ResultSet rs = ps.executeQuery()) { if (!rs.next()) throw new IllegalStateException("Row missing; nothing to update"); }
}

Try / catch

try {
    // update checkpoint
} catch (Exception e) {
    Throwable cause = e.getCause();
    if (cause instanceof java.sql.SQLTransientException || cause instanceof java.sql.SQLRecoverableException) {
        // retry with exponential backoff
    } else { throw e; }
}

Prevention

When it happens

Trigger: Updating an existing checkpoint when the UPDATE statement fails: connection loss, lock timeout from concurrent writers, constraint violation, or serializing the new state throws IOException.

Common situations: Concurrent graph runs contending for the same thread row; database failover mid-transaction; oversized state payload hitting Oracle limits; schema change altering the checkpoint table columns.

Related errors


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