apache/iceberg · warning · CommitFailedException

Cannot commit to table %s metadata location from %s to %s be

Error message

Cannot commit to table %s metadata location from %s to %s because it has been concurrently modified to %s

What it means

This is the optimistic-concurrency conflict of the in-memory catalog: the commit expected the table's metadata location to equal oldLocation, but the map holds a different existingLocation, meaning another committer advanced the table in between. Per Iceberg's commit contract, CommitFailedException signals the caller to refresh and retry the update against the new state.

Source

Thrown at core/src/main/java/org/apache/iceberg/inmemory/InMemoryCatalog.java:454

        if (views.containsKey(tableIdentifier)) {
          throw new AlreadyExistsException(
              "View with same name already exists: %s", tableIdentifier);
        }

        tables.compute(
            tableIdentifier,
            (k, existingLocation) -> {
              if (!Objects.equal(existingLocation, oldLocation)) {
                if (null == base) {
                  throw new AlreadyExistsException("Table already exists: %s", tableName());
                }

                if (null == existingLocation) {
                  throw new NoSuchTableException("Table does not exist: %s", tableName());
                }

                throw new CommitFailedException(
                    "Cannot commit to table %s metadata location from %s to %s "
                        + "because it has been concurrently modified to %s",
                    tableIdentifier, oldLocation, newLocation, existingLocation);
              }
              return newLocation;
            });
      }
    }

    @Override
    public FileIO io() {
      return fileIO;
    }

    @Override
    protected String tableName() {
      return fullTableName;
    }

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Retry the commit: catch CommitFailedException, call table.refresh(), re-apply the update, and commit again (Iceberg's RetryUtil/Tasks.foreach with retry works well).
  2. Reduce conflict windows by shortening transaction scopes or partitioning work across distinct tables.
  3. Ensure every retry reloads/refreshes metadata; never reuse stale TableMetadata.

Example fix

// before
table.updateSchema().addColumn("c", Types.IntegerType.get()).commit();

// after
Tasks.foreach(table)
    .retry(3)
    .onlyOn(CommitFailedException.class)
    .run(t -> t.refresh());
table.refresh();
table.updateSchema().addColumn("c", Types.IntegerType.get()).commit();
Defensive patterns

Strategy: retry

Try / catch

try { table.commit(update); } catch (CommitFailedException e) { table.refresh(); /* re-apply update on refreshed metadata and retry, bounded attempts */ }

Prevention

When it happens

Trigger: Two writers load the same table and both call commit(); the loser sees existingLocation != oldLocation and neither base nor existingLocation is null. Any concurrent metadata operation (schema update, append, partition spec change) on the same table.

Common situations: Multiple executors/threads appending to one table; concurrent schema evolution and writes; retry loops that do not refresh the table before recommitting.

Related errors


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