apache/iceberg · error · AlreadyExistsException

Table was created concurrently: %s

Error message

Table was created concurrently: %s

What it means

Thrown when create() passed the initial existence pre-check (ops.current() was null) but the subsequent ops.commit(null, metadata) failed with CommitFailedException, meaning another writer created the table between the check and the commit. This is the race-condition guard for concurrent table creation on metastore catalogs.

Source

Thrown at core/src/main/java/org/apache/iceberg/BaseMetastoreCatalog.java:203

      return this;
    }

    @Override
    public Table create() {
      TableOperations ops = newTableOps(identifier);
      if (ops.current() != null) {
        throw new AlreadyExistsException("Table already exists: %s", identifier);
      }

      String baseLocation = location != null ? location : defaultWarehouseLocation(identifier);
      tableProperties.putAll(tableOverrideProperties());
      TableMetadata metadata =
          TableMetadata.newTableMetadata(schema, spec, sortOrder, baseLocation, tableProperties);

      try {
        ops.commit(null, metadata);
      } catch (CommitFailedException ignored) {
        throw new AlreadyExistsException("Table was created concurrently: %s", identifier);
      }

      return new BaseTable(ops, fullTableName(name(), identifier), metricsReporter());
    }

    @Override
    public Transaction createTransaction() {
      TableOperations ops = newTableOps(identifier);
      if (ops.current() != null) {
        throw new AlreadyExistsException("Table already exists: %s", identifier);
      }

      String baseLocation = location != null ? location : defaultWarehouseLocation(identifier);
      tableProperties.putAll(tableOverrideProperties());
      TableMetadata metadata =
          TableMetadata.newTableMetadata(schema, spec, sortOrder, baseLocation, tableProperties);
      return Transactions.createTableTransaction(
          identifier.toString(), ops, metadata, metricsReporter());

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Catch AlreadyExistsException and treat creation as successful (load the existing table)
  2. Retry the whole operation, this time taking the load-existing path
  3. Serialize DDL through a single pipeline/scheduler so only one writer creates tables
  4. Use catalog-level locking or a coordination mechanism for table creation

Example fix

// before
catalog.buildTable(ident, schema).create();
// after
try {
  catalog.buildTable(ident, schema).create();
} catch (AlreadyExistsException e) {
  Table table = catalog.loadTable(ident); // lost a creation race; use the winner's table
}
Defensive patterns

Strategy: try-catch

Try / catch

try { catalog.buildTable(ident, schema).create(); }
catch (AlreadyExistsException e) { /* lost creation race; load the table */ table = catalog.loadTable(ident); }

Prevention

When it happens

Trigger: Two processes/threads call create() for the same table identifier concurrently; the losing committer's null-base commit fails and is converted to this AlreadyExistsException.

Common situations: Parallel Spark/Flink jobs or schedulers launching the same DDL simultaneously; orchestrator retries after a timeout where the first attempt actually succeeded; multi-region or shared metastore setups with competing writers.

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/8eb379a13424a98d. Report an issue: GitHub.