apache/iceberg · error · CommitFailedException

Requirement failed: %s %s has changed: expected id %s != %s

Error message

Requirement failed: %s %s has changed: expected id %s != %s

What it means

Thrown by AssertRefSnapshotId.validate when the requirement asserts a branch/tag points at a specific snapshot ID, but the ref exists with a different snapshot ID. The 'expected id %s != %s' values are the asserted snapshot ID versus the one actually in the base metadata. This is optimistic-concurrency validation: someone moved or created the ref between planning and commit, so the commit is rejected.

Source

Thrown at core/src/main/java/org/apache/iceberg/UpdateRequirement.java:118

    public String refName() {
      return name;
    }

    public Long snapshotId() {
      return snapshotId;
    }

    @Override
    public void validate(TableMetadata base) {
      SnapshotRef ref = base.ref(name);
      if (ref != null) {
        String type = ref.isBranch() ? "branch" : "tag";
        if (snapshotId == null) {
          // a null snapshot ID means the ref should not exist already
          throw new CommitFailedException(
              "Requirement failed: %s %s was created concurrently", type, name);
        } else if (snapshotId != ref.snapshotId()) {
          throw new CommitFailedException(
              "Requirement failed: %s %s has changed: expected id %s != %s",
              type, name, snapshotId, ref.snapshotId());
        }
      } else if (snapshotId != null) {
        throw new CommitFailedException(
            "Requirement failed: branch or tag %s is missing, expected %s", name, snapshotId);
      }
    }
  }

  class AssertLastAssignedFieldId implements UpdateRequirement {
    private final int lastAssignedFieldId;

    public AssertLastAssignedFieldId(int lastAssignedFieldId) {
      this.lastAssignedFieldId = lastAssignedFieldId;
    }

    public int lastAssignedFieldId() {

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Refresh the table metadata (reload the Table), rebuild the requirements from the current ref snapshot ID, and retry the commit.
  2. Base the update on the latest branch head instead of a cached snapshot reference (use refresh() before computing the operation).
  3. If the ref was moved intentionally by another process, re-plan the operation against the new snapshot and drop the stale assertion.
  4. Reduce the planning-to-commit window to lower conflict probability; retry with backoff is the expected pattern for CommitFailedException.

Example fix

// before
table.refresh(); // done too late / not at all; stale snapshotId used
// after
table.refresh();
long head = table.ref("main").snapshotId();
List<UpdateRequirement> reqs =
    ImmutableList.of(new UpdateRequirement.AssertRefSnapshotId("main", head));
Defensive patterns

Strategy: retry

Validate before calling

table.refresh();
SnapshotRef ref = table.ref("main");
if (ref == null || ref.snapshotId() != expectedSnapshotId) {
  throw new IllegalStateException("Branch moved; re-plan before commit");
}

Try / catch

try {
  commitWithRequirements(reqs);
} catch (CommitFailedException e) {
  if (e.getMessage().contains("has changed")) {
    table.refresh();
    reqs = rebuildRequirements(table); // retry with backoff
  } else { throw e; }
}

Prevention

When it happens

Trigger: Committing UpdateRequirements containing AssertRefSnapshotId(name, snapshotId) when base.ref(name).snapshotId() differs — e.g. another writer advanced the branch via commits, fast-forwarded it, or overwrote the tag before this update validated.

Common situations: A writer planning an operation against snapshot N while a concurrent append moves the branch to N+1; RETAIN/remove-and-recreate of a tag by another job; long-running Spark/Flink jobs whose planned requirements go stale before commit; REST catalog conflict when two systems manage the same branch.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/d6f097bb2cbb62cb. Report an issue: GitHub.