{"record":{"id":"d6f097bb2cbb62cb","repo":"apache/iceberg","slug":"requirement-failed-s-s-has-changed-expected-id","errorCode":null,"errorMessage":"Requirement failed: %s %s has changed: expected id %s != %s","messagePattern":"Requirement failed: (.+?) (.+?) has changed: expected id (.+?) != (.+?)","errorType":"exception","errorClass":"CommitFailedException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/UpdateRequirement.java","lineNumber":118,"sourceCode":"    public String refName() {\n      return name;\n    }\n\n    public Long snapshotId() {\n      return snapshotId;\n    }\n\n    @Override\n    public void validate(TableMetadata base) {\n      SnapshotRef ref = base.ref(name);\n      if (ref != null) {\n        String type = ref.isBranch() ? \"branch\" : \"tag\";\n        if (snapshotId == null) {\n          // a null snapshot ID means the ref should not exist already\n          throw new CommitFailedException(\n              \"Requirement failed: %s %s was created concurrently\", type, name);\n        } else if (snapshotId != ref.snapshotId()) {\n          throw new CommitFailedException(\n              \"Requirement failed: %s %s has changed: expected id %s != %s\",\n              type, name, snapshotId, ref.snapshotId());\n        }\n      } else if (snapshotId != null) {\n        throw new CommitFailedException(\n            \"Requirement failed: branch or tag %s is missing, expected %s\", name, snapshotId);\n      }\n    }\n  }\n\n  class AssertLastAssignedFieldId implements UpdateRequirement {\n    private final int lastAssignedFieldId;\n\n    public AssertLastAssignedFieldId(int lastAssignedFieldId) {\n      this.lastAssignedFieldId = lastAssignedFieldId;\n    }\n\n    public int lastAssignedFieldId() {","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/UpdateRequirement.java#L100-L136","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ntable.refresh(); // done too late / not at all; stale snapshotId used\n// after\ntable.refresh();\nlong head = table.ref(\"main\").snapshotId();\nList<UpdateRequirement> reqs =\n    ImmutableList.of(new UpdateRequirement.AssertRefSnapshotId(\"main\", head));","handlingStrategy":"retry","validationCode":"table.refresh();\nSnapshotRef ref = table.ref(\"main\");\nif (ref == null || ref.snapshotId() != expectedSnapshotId) {\n  throw new IllegalStateException(\"Branch moved; re-plan before commit\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  commitWithRequirements(reqs);\n} catch (CommitFailedException e) {\n  if (e.getMessage().contains(\"has changed\")) {\n    table.refresh();\n    reqs = rebuildRequirements(table); // retry with backoff\n  } else { throw e; }\n}","preventionTips":["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"],"tags":["concurrency","commit-failed","snapshot-ref","iceberg"],"backgroundTag":"invalid-state-transition","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}