apache/iceberg · error · AlreadyExistsException
Table already exists: %s
Error message
Table already exists: %s
What it means
Inside the tables.compute lambda of doCommit, if the expected existing location does not match the map's current value and base is null (a create operation), the table must have been created concurrently. InMemoryCatalog throws AlreadyExistsException rather than overwriting the concurrent creation, since two 'create table' commits raced.
Source
Thrown at core/src/main/java/org/apache/iceberg/inmemory/InMemoryCatalog.java:447
synchronized (InMemoryCatalog.this) {
if (null == base && !namespaceExists(tableIdentifier.namespace())) {
throw new NoSuchNamespaceException(
"Cannot create table %s. Namespace does not exist: %s",
tableIdentifier, tableIdentifier.namespace());
}
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() {View on GitHub (pinned to 86d9c8fc54)
Solutions
- Serialize or deduplicate createTable calls for the same identifier (e.g. synchronize on the identifier).
- Treat AlreadyExistsException as success in idempotent create flows and load the existing table instead.
- Retry the logical operation with a load-then-verify pattern: catch, then catalog.loadTable(ident) and check the schema matches expectations.
Example fix
// before
catalog.createTable(ident, schema);
// after
try {
catalog.createTable(ident, schema);
} catch (AlreadyExistsException e) {
// concurrent create won; verify it is the table we wanted
catalog.loadTable(ident);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Cannot be fully pre-checked under concurrency; optionally: if (catalog.tableExists(ident)) { ... load instead of create ... } Try / catch
try { catalog.createTable(ident, schema); } catch (AlreadyExistsException e) { Table existing = catalog.loadTable(ident); // verify or adopt } Prevention
- Deduplicate create calls with an in-process set of in-flight identifiers
- Make creates idempotent: catch AlreadyExistsException and verify the existing table
- Avoid parallel setup threads touching the same identifiers
When it happens
Trigger: Two concurrent catalog.createTable(ident, ...) calls for the same identifier: the first wins the map insert, the second finds existingLocation != null while its base == null and fails.
Common situations: Parallel test setup threads creating the same table; idempotent create retries racing; duplicate jobs submitting identical table creation.
Related errors
- View already exists: %s
- Table was created concurrently: %s
- Table already exists: %s
- Table already exists: %s
- Cannot create table %s. Namespace does not exist: %s
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/4418e2d521622165.
Report an issue: GitHub.