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

  1. Call viewOps.refresh() and re-apply the change on the fresh metadata, then retry the commit
  2. Serialize writers to the same view (single writer per view, or external locking)
  3. Investigate the concurrent writer — check which process committed between your read and commit
  4. 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

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


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