apache/iceberg · warning · CommitFailedException
Cannot commit %s: metadata location %s has changed from %s
Error message
Cannot commit %s: metadata location %s has changed from %s
What it means
validateMetadataLocation performs optimistic-concurrency validation: before updating an existing view, the metadata location recorded in the JDBC catalog is compared to the location of the caller's base ViewMetadata. If they differ, a CommitFailedException is thrown because another committer changed the view since this operation's base was read.
Source
Thrown at core/src/main/java/org/apache/iceberg/jdbc/JdbcViewOperations.java:153
}
}
@Override
protected String viewName() {
return viewIdentifier.toString();
}
@Override
protected FileIO io() {
return fileIO;
}
private void validateMetadataLocation(Map<String, String> view, ViewMetadata base) {
String catalogMetadataLocation = view.get(JdbcTableOperations.METADATA_LOCATION_PROP);
String baseMetadataLocation = base != null ? base.metadataFileLocation() : null;
if (!Objects.equals(baseMetadataLocation, catalogMetadataLocation)) {
throw new CommitFailedException(
"Cannot commit %s: metadata location %s has changed from %s",
viewIdentifier, baseMetadataLocation, catalogMetadataLocation);
}
}
private void updateView(String newMetadataLocation, String oldMetadataLocation)
throws SQLException, InterruptedException {
int updatedRecords =
JdbcUtil.updateView(
connections, catalogName, viewIdentifier, newMetadataLocation, oldMetadataLocation);
if (updatedRecords == 1) {
LOG.debug("Successfully committed to existing view: {}", viewIdentifier);
} else {
throw new CommitFailedException(
"Failed to update view %s from catalog %s", viewIdentifier, catalogName);
}
}View on GitHub (pinned to 86d9c8fc54)
Solutions
- Call viewOps.refresh() and re-apply the change on the fresh metadata, then retry the commit
- Serialize writers to the same view (single writer per view, or external locking)
- Investigate the concurrent writer — check which process committed between your read and commit
- Catch CommitFailedException and retry with backoff, as it is designed to signal retryable conflicts
Example fix
// before: commit stale base
ViewMetadata base = loadOnce();
commit(view, base); // CommitFailedException
// after: refresh and retry
try {
commit(view, base);
} catch (CommitFailedException e) {
viewOps.refresh();
commit(applyChange(viewOps.current()), viewOps.current());
} Defensive patterns
Strategy: retry
Validate before calling
// ensure your base is fresh right before commit
viewOps.refresh();
if (!Objects.equals(base.metadataFileLocation(),
viewOps.current().metadataFileLocation())) {
throw new IllegalStateException("Base is stale; rebase before commit");
} Try / catch
try {
viewOps.commit(request);
} catch (CommitFailedException e) {
viewOps.refresh();
retryCommitWithBackoff(); // re-apply change on fresh metadata
} Prevention
- Always refresh() before committing long-lived view objects
- Use a single writer per view or external locking when multiple engines write the same view
- Catch CommitFailedException and retry with exponential backoff
- Avoid drop+recreate of views that other processes hold open
When it happens
Trigger: Calling JdbcViewOperations.commit() with a base ViewMetadata whose metadataFileLocation does not match JdbcTableOperations.METADATA_LOCATION_PROP currently stored in the catalog — i.e. a concurrent commit landed between refresh() and commit().
Common situations: Two engines (Spark + Trino) or two jobs writing the same view concurrently; a long-running operation holding a stale view object while someone else recreates the view; recreating the view (drop + create) while another process holds it open.
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
- Failed to update view %s from catalog %s
- Cannot commit to table %s metadata location from %s to %s be
- Namespace does not exist: %s
- View does not exist: %s
- View with same name already exists: %s
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/291c6953dac60465.
Report an issue: GitHub.