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
- Refresh the table metadata (reload the Table), rebuild the requirements from the current ref snapshot ID, and retry the commit.
- Base the update on the latest branch head instead of a cached snapshot reference (use refresh() before computing the operation).
- If the ref was moved intentionally by another process, re-plan the operation against the new snapshot and drop the stale assertion.
- 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
- Call table.refresh() right before building requirements
- Keep the plan-to-commit window short; avoid long-running planning against a cached Table
- Treat CommitFailedException as retriable and implement backoff retry
- Avoid multiple independent jobs moving the same branch/tag
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
- Requirement failed: branch or tag %s is missing, expected %s
- Requirement failed: %s %s was created concurrently
- Requirement failed: last assigned field id changed: expected
- Requirement failed: current schema changed: expected id %s !
- Requirement failed: last assigned partition id changed: expe
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/d6f097bb2cbb62cb.
Report an issue: GitHub.