{"record":{"id":"0e754634fe7693cc","repo":"apache/iceberg","slug":"cannot-commit-s-metadata-location-s-has-changed","errorCode":null,"errorMessage":"Cannot commit %s: metadata location %s has changed from %s","messagePattern":"Cannot commit (.+?): metadata location (.+?) has changed from (.+?)","errorType":"exception","errorClass":"CommitFailedException","httpStatus":null,"severity":"warning","filePath":"core/src/main/java/org/apache/iceberg/jdbc/JdbcTableOperations.java","lineNumber":205,"sourceCode":"            catalogName,\n            namespace,\n            tableIdentifier,\n            newMetadataLocation);\n\n    if (insertRecord == 1) {\n      LOG.debug(\"Successfully committed to new table: {}\", tableIdentifier);\n    } else {\n      throw new CommitFailedException(\n          \"Failed to create table %s in catalog %s\", tableIdentifier, catalogName);\n    }\n  }\n\n  private void validateMetadataLocation(Map<String, String> table, TableMetadata base) {\n    String catalogMetadataLocation = table.get(METADATA_LOCATION_PROP);\n    String baseMetadataLocation = base != null ? base.metadataFileLocation() : null;\n\n    if (!Objects.equals(baseMetadataLocation, catalogMetadataLocation)) {\n      throw new CommitFailedException(\n          \"Cannot commit %s: metadata location %s has changed from %s\",\n          tableIdentifier, baseMetadataLocation, catalogMetadataLocation);\n    }\n  }\n\n  @Override\n  public FileIO io() {\n    return fileIO;\n  }\n\n  @Override\n  protected String tableName() {\n    return tableIdentifier.toString();\n  }\n}\n","sourceCodeStart":187,"sourceCodeEnd":221,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/jdbc/JdbcTableOperations.java#L187-L221","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nTable cached = catalog.loadTable(ident); // long-lived\n// ... hours later ...\ncached.newDelete().deleteFromRowFilter(expr).commit(); // stale base -> CommitFailedException\n// after\nTable fresh = catalog.loadTable(ident); // reload right before each commit\nfresh.newDelete().deleteFromRowFilter(expr).commit();","handlingStrategy":"retry","validationCode":"table.refresh(); // reload so base.metadataFileLocation() matches current catalog state","typeGuard":null,"tryCatchPattern":"Tasks.foreach(table).retry(20).onlyRetryOn(CommitFailedException.class).run(t -> t.newAppend().appendFile(f).commit());","preventionTips":["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"],"tags":["jdbc","optimistic-concurrency","stale-metadata","commit-retry"],"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"}