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
- Catch AlreadyExistsException and treat creation as successful (load the existing table)
- Retry the whole operation, this time taking the load-existing path
- Serialize DDL through a single pipeline/scheduler so only one writer creates tables
- 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
- Treat creation races as benign and fall back to loading the existing table
- Serialize DDL through a single orchestrator
- Retry on CommitFailedException with fresh metadata
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
- Table already exists: %s
- Table already exists: %s
- View already exists: %s
- Database %s already exists in the iceberg catalog %s.
- Table %s already exists in the database %s and catalog %s
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/8eb379a13424a98d.
Report an issue: GitHub.