apache/iceberg · error · AlreadyExistsException
Table already exists: %s
Error message
Table already exists: %s
What it means
CachingCatalog.create() uses an atomic compute-if-absent over the cache to build the table; if the table was already created by another thread/committer between the caller's request and creation (created flag stays false), the catalog throws AlreadyExistsException. This is the standard 409-style 'already exists' outcome for concurrent creation.
Source
Thrown at core/src/main/java/org/apache/iceberg/CachingCatalog.java:275
@Override
public TableBuilder withProperty(String key, String value) {
innerBuilder.withProperty(key, value);
return this;
}
@Override
public Table create() {
AtomicBoolean created = new AtomicBoolean(false);
Table table =
tableCache.get(
canonicalizeIdentifier(ident),
identifier -> {
created.set(true);
return innerBuilder.create();
});
if (!created.get()) {
throw new AlreadyExistsException("Table already exists: %s", ident);
}
return table;
}
@Override
public Transaction createTransaction() {
// create a new transaction without altering the cache. the table doesn't exist until the
// transaction is
// committed. if the table is created before the transaction commits, any cached version is
// correct and the
// transaction create will fail. if the transaction commits before another create, then the
// cache will be empty.
return innerBuilder.createTransaction();
}
@Override
public Transaction replaceTransaction() {View on GitHub (pinned to 86d9c8fc54)
Solutions
- Treat AlreadyExistsException as success in idempotent create workflows
- Fall back to catalog.loadTable(ident) when create fails with already-exists
- Coordinate table creation in a single setup step/job before writers start
- Use createOrReplaceTransaction if replacement is intended
Example fix
// before
Table t = catalog.createTable(ident, schema);
// after
Table t;
try {
t = catalog.createTable(ident, schema);
} catch (AlreadyExistsException e) {
t = catalog.loadTable(ident);
} Defensive patterns
Strategy: try-catch
Validate before calling
if (catalog.tableExists(ident)) { return catalog.loadTable(ident); } // still racy; keep the catch Try / catch
catch (AlreadyExistsException e) { return catalog.loadTable(ident); } Prevention
- Never assume createTable is concurrency-safe — always handle AlreadyExistsException
- Gate table creation behind a single orchestrating job
- Prefer load-after-create fallback patterns in shared catalogs
When it happens
Trigger: Calling catalog.createTable(ident, ...) or the table builder's create() concurrently from two jobs/threads with the same identifier; a retrying job re-attempting creation after a prior partial success.
Common situations: Two Spark/Flink jobs creating the same table at startup; idempotency retries racing with the first successful create; multi-cluster writers against a shared catalog.
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
- Cannot create namespace %s: already exists
- Cannot rename table %s to %s: %s already exists
- Table already exists: %s
- Namespace already exists: %s
- Table already exists: %s
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/711723f423c68125.
Report an issue: GitHub.