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 compares the catalog's current METADATA_LOCATION_PROP with the metadata file location of the client's base snapshot. On mismatch it throws CommitFailedException: the table changed elsewhere between this client's refresh and its commit, so applying the update would clobber someone else's commit. Callers are expected to refresh and retry.
Source
Thrown at core/src/main/java/org/apache/iceberg/jdbc/JdbcTableOperations.java:205
catalogName,
namespace,
tableIdentifier,
newMetadataLocation);
if (insertRecord == 1) {
LOG.debug("Successfully committed to new table: {}", tableIdentifier);
} else {
throw new CommitFailedException(
"Failed to create table %s in catalog %s", tableIdentifier, catalogName);
}
}
private void validateMetadataLocation(Map<String, String> table, TableMetadata base) {
String catalogMetadataLocation = table.get(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",
tableIdentifier, baseMetadataLocation, catalogMetadataLocation);
}
}
@Override
public FileIO io() {
return fileIO;
}
@Override
protected String tableName() {
return tableIdentifier.toString();
}
}
View on GitHub (pinned to 86d9c8fc54)
Solutions
- Refresh before commit: table.refresh() (or reload via catalog.loadTable) so base matches the catalog, then reapply and commit — Iceberg's built-in commit retry does this; avoid caching Table objects across commits
- Increase retry attempts on the commit (CommitState / Tasks.retry with onlyRetryOn(CommitFailedException))
- Stop external edits to jdbc_tables.metadata_location; do all changes through the catalog API
- If retries keep failing, serialize writers or partition workloads so fewer writers target the same table
Example fix
// before Table cached = catalog.loadTable(ident); // long-lived // ... hours later ... cached.newDelete().deleteFromRowFilter(expr).commit(); // stale base -> CommitFailedException // after Table fresh = catalog.loadTable(ident); // reload right before each commit fresh.newDelete().deleteFromRowFilter(expr).commit();
Defensive patterns
Strategy: retry
Validate before calling
table.refresh(); // reload so base.metadataFileLocation() matches current catalog state
Try / catch
Tasks.foreach(table).retry(20).onlyRetryOn(CommitFailedException.class).run(t -> t.newAppend().appendFile(f).commit());
Prevention
- Never cache Table instances across long job lifetimes; reload before each commit
- Rely on Iceberg's automatic CommitFailedException retry loop
- Prohibit manual edits to jdbc_tables.metadata_location
- Reduce concurrent writers per table via scheduling or table partitioning
When it happens
Trigger: Another engine/job committed to the table between loadTable/refresh and commit; a table was dropped and recreated so the location changed; manual manipulation of the metadata_location column; stale Table instance cached across a long-running job and committed at the end.
Common situations: Long-running Spark/Flink jobs holding a stale Table handle while streaming writers commit continuously; multiple writers without Iceberg's retry loop; operator manually repairing catalog rows in the DB.
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 table %s from catalog %s
- Failed to create table %s in catalog %s
- Cannot commit %s: metadata location %s has changed from %s
- Failed to update view %s from catalog %s
- Cannot commit: stale table metadata
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/0e754634fe7693cc.
Report an issue: GitHub.