apache/iceberg · error · AlreadyExistsException

Table already exists: %s

Error message

Table already exists: %s

What it means

When commit() is called with base == null (a table-creation commit) but current() is non-null, the table already exists, so creation is rejected with AlreadyExistsException. The reference comparison base != current() distinguishes create (base null) from update (base non-null) and routes each failure to its specific error.

Source

Thrown at core/src/main/java/org/apache/iceberg/BaseMetastoreTableOperations.java:117

      throw e;
    }
    return current();
  }

  protected void doRefresh() {
    throw new UnsupportedOperationException("Not implemented: doRefresh");
  }

  @Override
  public void commit(TableMetadata base, TableMetadata metadata) {
    // if the metadata is already out of date, reject it
    if (base != current()) {
      if (base != null) {
        throw new CommitFailedException("Cannot commit: stale table metadata");
      } else {
        // when current is non-null, the table exists. but when base is null, the commit is trying
        // to create the table
        throw new AlreadyExistsException("Table already exists: %s", tableName());
      }
    }
    // if the metadata is not changed, return early
    if (base == metadata) {
      LOG.info("Nothing to commit.");
      return;
    }

    long start = System.currentTimeMillis();
    doCommit(base, metadata);
    CatalogUtil.deleteRemovedMetadataFiles(io(), base, metadata);
    requestRefresh();

    LOG.info(
        "Successfully committed to table {} in {} ms",
        tableName(),
        System.currentTimeMillis() - start);
  }

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Catch AlreadyExistsException and load the existing table instead of creating
  2. Call refresh() and verify current() == null before attempting a creation commit
  3. Use catalog.buildTable(...).create(), which wraps this logic with clearer errors
  4. Ensure only one writer performs creation DDL for a given identifier

Example fix

// before
ops.commit(null, metadata); // may throw if table now exists
// after
ops.refresh();
if (ops.current() != null) {
  table = new BaseTable(ops, name); // already created by someone else
} else {
  try { ops.commit(null, metadata); }
  catch (AlreadyExistsException e) { table = new BaseTable(ops, name); }
}
Defensive patterns

Strategy: try-catch

Validate before calling

ops.refresh(); if (ops.current() != null) { /* table exists; don't attempt creation commit */ }

Try / catch

try { ops.commit(null, metadata); }
catch (AlreadyExistsException e) { /* concurrent creation won; load existing table */ }

Prevention

When it happens

Trigger: Calling ops.commit(null, metadata) to create a table that was registered in the metastore after the caller last checked, or calling commit(null, ...) on operations for an existing table without refreshing.

Common situations: Lost creation race between concurrent DDL writers; reusing TableOperations for a table that already exists when attempting create-style commits; catalog pre-check (ops.current() == null) that became stale before commit.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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