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
- Re-read the view (catalog.loadView) to get fresh metadata, then retry the replace
- Coordinate writers so only one process replaces a given view at a time
- Use a replaceTransaction with proper row-level conflict handling if this races frequently
- 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
- Serialize view replacements through a single writer or coordination mechanism
- Retry replace operations after refreshing metadata
- Keep view update transactions short to shrink the race window
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
- Cannot commit to table %s metadata location from %s to %s be
- Cannot commit to view %s metadata location from %s to %s bec
- Cannot commit %s: concurrent update detected
- Failed to commit
- Cannot commit file that conflicts with existing partition: %
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/3f1071c6056d436b.
Report an issue: GitHub.