{"record":{"id":"5e419354856069d2","repo":"apache/iceberg","slug":"table-already-exists-s-5e4193","errorCode":null,"errorMessage":"Table already exists: %s","messagePattern":"Table already exists: (.+?)","errorType":"exception","errorClass":"AlreadyExistsException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/BaseMetastoreTableOperations.java","lineNumber":117,"sourceCode":"      throw e;\n    }\n    return current();\n  }\n\n  protected void doRefresh() {\n    throw new UnsupportedOperationException(\"Not implemented: doRefresh\");\n  }\n\n  @Override\n  public void commit(TableMetadata base, TableMetadata metadata) {\n    // if the metadata is already out of date, reject it\n    if (base != current()) {\n      if (base != null) {\n        throw new CommitFailedException(\"Cannot commit: stale table metadata\");\n      } else {\n        // when current is non-null, the table exists. but when base is null, the commit is trying\n        // to create the table\n        throw new AlreadyExistsException(\"Table already exists: %s\", tableName());\n      }\n    }\n    // if the metadata is not changed, return early\n    if (base == metadata) {\n      LOG.info(\"Nothing to commit.\");\n      return;\n    }\n\n    long start = System.currentTimeMillis();\n    doCommit(base, metadata);\n    CatalogUtil.deleteRemovedMetadataFiles(io(), base, metadata);\n    requestRefresh();\n\n    LOG.info(\n        \"Successfully committed to table {} in {} ms\",\n        tableName(),\n        System.currentTimeMillis() - start);\n  }","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/BaseMetastoreTableOperations.java#L99-L135","documentation":"When commit() is called with base == null (a table-creation commit) but current() is non-null, the table already exists, so creation is rejected with AlreadyExistsException. The reference comparison base != current() distinguishes create (base null) from update (base non-null) and routes each failure to its specific error.","triggerScenarios":"Calling ops.commit(null, metadata) to create a table that was registered in the metastore after the caller last checked, or calling commit(null, ...) on operations for an existing table without refreshing.","commonSituations":"Lost creation race between concurrent DDL writers; reusing TableOperations for a table that already exists when attempting create-style commits; catalog pre-check (ops.current() == null) that became stale before commit.","solutions":["Catch AlreadyExistsException and load the existing table instead of creating","Call refresh() and verify current() == null before attempting a creation commit","Use catalog.buildTable(...).create(), which wraps this logic with clearer errors","Ensure only one writer performs creation DDL for a given identifier"],"exampleFix":"// before\nops.commit(null, metadata); // may throw if table now exists\n// after\nops.refresh();\nif (ops.current() != null) {\n  table = new BaseTable(ops, name); // already created by someone else\n} else {\n  try { ops.commit(null, metadata); }\n  catch (AlreadyExistsException e) { table = new BaseTable(ops, name); }\n}","handlingStrategy":"try-catch","validationCode":"ops.refresh(); if (ops.current() != null) { /* table exists; don't attempt creation commit */ }","typeGuard":null,"tryCatchPattern":"try { ops.commit(null, metadata); }\ncatch (AlreadyExistsException e) { /* concurrent creation won; load existing table */ }","preventionTips":["Never assume a pre-check of absence stays valid; re-check at commit","Use catalog create APIs that handle the race","Limit creation DDL to a single writer"],"tags":["catalog","table-exists","race-condition"],"backgroundTag":"file-already-exists","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}