apache/iceberg · warning · CommitFailedException

Failed to update table %s from catalog %s

Error message

Failed to update table %s from catalog %s

What it means

updateTable performs an atomic UPDATE of the metadata location guarded by the old location (WHERE metadata_location = oldMetadataLocation). If exactly one row is not updated (updatedRecords != 1) the concurrent state no longer matches the base, so a CommitFailedException is thrown. This is Iceberg's normal optimistic-concurrency retry signal: the caller (BaseTable/CommitState) is expected to refresh and retry the commit.

Source

Thrown at core/src/main/java/org/apache/iceberg/jdbc/JdbcTableOperations.java:160

      throw new UncheckedInterruptedException(e, "Interrupted during commit");
    }
  }

  private void updateTable(String newMetadataLocation, String oldMetadataLocation)
      throws SQLException, InterruptedException {
    int updatedRecords =
        JdbcUtil.updateTable(
            schemaVersion,
            connections,
            catalogName,
            tableIdentifier,
            newMetadataLocation,
            oldMetadataLocation);

    if (updatedRecords == 1) {
      LOG.debug("Successfully committed to existing table: {}", tableIdentifier);
    } else {
      throw new CommitFailedException(
          "Failed to update table %s from catalog %s", tableIdentifier, catalogName);
    }
  }

  private void createTable(String newMetadataLocation) throws SQLException, InterruptedException {
    Namespace namespace = tableIdentifier.namespace();
    if (PropertyUtil.propertyAsBoolean(catalogProperties, JdbcUtil.STRICT_MODE_PROPERTY, false)
        && !JdbcUtil.namespaceExists(catalogName, connections, namespace)) {
      throw new NoSuchNamespaceException(
          "Cannot create table %s in catalog %s. Namespace %s does not exist",
          tableIdentifier, catalogName, namespace);
    }

    if (schemaVersion == JdbcUtil.SchemaVersion.V1
        && JdbcUtil.viewExists(catalogName, connections, tableIdentifier)) {
      throw new AlreadyExistsException("View with same name already exists: %s", tableIdentifier);
    }

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Retry the commit with a refreshed table: load the table again so base reflects the latest metadata, then reapply changes (Iceberg's commit retry loop does this automatically)
  2. Reduce write contention or partition writes so concurrent commits to one table are rare
  3. Check identifier casing against the DB's case-sensitivity rules if retries never succeed
  4. If the table was recreated, re-point the job at the new table instead of committing stale state

Example fix

// before
Table table = catalog.loadTable(ident);
table.refresh();
table.newAppend().appendFile(f).commit(); // single shot, fails on race
// after
Tasks.foreach(table)
    .retry(10)
    .onlyRetryOn(CommitFailedException.class)
    .run(t -> t.newAppend().appendFile(f).commit());
Defensive patterns

Strategy: retry

Validate before calling

table.refresh(); // ensure base metadata location matches catalog before commit

Try / catch

Tasks.foreach(table).retry(10).onlyRetryOn(CommitFailedException.class).run(t -> t.transaction().commitTransaction());

Prevention

When it happens

Trigger: Two writers committed between this job's refresh and its commit, so the row's metadata_location no longer equals the base location; the table was dropped and recreated; the UPDATE matched 0 rows due to wrong tableIdentifier/catalogName casing on case-sensitive DBs.

Common situations: Multiple Spark/Flink jobs writing to the same table concurrently; a compaction job racing with streaming ingest; manual metadata file edits; case-sensitive Postgres vs case-insensitive MySQL identifier mismatches.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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