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

  1. Serialize or deduplicate createTable calls for the same identifier (e.g. synchronize on the identifier).
  2. Treat AlreadyExistsException as success in idempotent create flows and load the existing table instead.
  3. 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

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


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