apache/iceberg · error · AlreadyExistsException

View was updated concurrently: %s

Error message

View was updated concurrently: %s

What it means

This AlreadyExistsException is thrown when a createOrReplace or replace operation's commit fails because the view's metadata changed concurrently (CommitFailedException). The catalog cannot guarantee a clean replacement, so it reports that another writer updated the view between this operation's read and commit.

Source

Thrown at core/src/main/java/org/apache/iceberg/view/BaseMetastoreViewCatalog.java:271

              .build();

      properties.putAll(viewOverrideProperties());

      ViewMetadata.Builder builder =
          ViewMetadata.buildFrom(metadata)
              .setProperties(properties)
              .setCurrentVersion(viewVersion, schema);

      if (null != location) {
        builder.setLocation(location);
      }

      ViewMetadata replacement = builder.build();

      try {
        ops.commit(metadata, replacement);
      } catch (CommitFailedException ignored) {
        throw new AlreadyExistsException("View was updated concurrently: %s", identifier);
      }

      return new BaseView(ops, ViewUtil.fullViewName(name(), identifier));
    }
  }

  @Override
  public TableBuilder buildTable(TableIdentifier identifier, Schema schema) {
    return new BaseMetastoreViewCatalogTableBuilder(identifier, schema);
  }

  /** The purpose of this class is to add view detection when replacing a table */
  protected class BaseMetastoreViewCatalogTableBuilder extends BaseMetastoreCatalogTableBuilder {
    private final TableIdentifier identifier;

    public BaseMetastoreViewCatalogTableBuilder(TableIdentifier identifier, Schema schema) {
      super(identifier, schema);
      this.identifier = identifier;

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Re-read the view (catalog.loadView) to get fresh metadata, then retry the replace
  2. Coordinate writers so only one process replaces a given view at a time
  3. Use a replaceTransaction with proper row-level conflict handling if this races frequently
  4. Check the underlying metastore for unusual concurrent writer activity or misconfigured retries

Example fix

// before
catalog.replaceView(identifier).withQuery("sql", query).createOrReplace();
// after
if (catalog.viewExists(identifier)) {
  try {
    catalog.replaceView(identifier).withQuery("sql", query).createOrReplace();
  } catch (AlreadyExistsException e) {
    // view changed concurrently; refresh and retry
    catalog.replaceView(identifier).withQuery("sql", query).createOrReplace();
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

boolean canReplace = catalog.viewExists(identifier); // and ensure single-writer coordination externally

Try / catch

try { catalog.replaceView(ident).withQuery("sql", q).createOrReplace(); } catch (AlreadyExistsException e) { // reload view and retry }

Prevention

When it happens

Trigger: Calling catalog.replaceView(...) or createOrReplace on a view while another client/process modifies or deletes the same view; the optimistic-concurrency commit in ops.commit raises CommitFailedException and this message is thrown instead.

Common situations: Multiple jobs or engine sessions (Spark, Trino, Flink) writing the same view definition concurrently; retry storms after a failed commit; long-running replacement transactions racing with quick updates.

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/3f1071c6056d436b. Report an issue: GitHub.