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

  1. 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
  2. Increase retry attempts on the commit (CommitState / Tasks.retry with onlyRetryOn(CommitFailedException))
  3. Stop external edits to jdbc_tables.metadata_location; do all changes through the catalog API
  4. 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

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


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/0e754634fe7693cc. Report an issue: GitHub.