apache/iceberg · error · DuplicateWAPCommitException
DuplicateWAPCommitException(wapId)
Error message
DuplicateWAPCommitException(wapId)
What it means
WapUtil.validateWapPublish checks whether a staged write-audit-publish (WAP) snapshot being published carries a wap.id whose value has already been published. Publishing the same staged snapshot twice would duplicate the commit, so it throws DuplicateWAPCommitException with the offending wapId.
Solutions
- Generate a unique wap.id per run (e.g. UUID) rather than reusing a fixed value
- Check WapUtil.validateWapPublish before calling publishWap and skip if the id is already published
- Catch DuplicateWAPCommitException and treat the publish as already complete (idempotent handling)
- Ensure only one job owns publishing for a given wap.id (locking/coordination)
Example fix
// before String wapId = "daily-ingest"; // reused every run table.publishWap(); // after String wapId = UUID.randomUUID().toString(); table.properties().put(TableProperties.WRITE_WAP_ID, wapId); // ... fastAppend ... WapUtil.validateWapPublish(ops.refresh().current(), stagedId); // throws if already published table.publishWap();
Defensive patterns
Strategy: try-catch
Validate before calling
String wapId = WapUtil.stagedWapId(table.currentSnapshot().snapshot(table.currentSnapshot().snapshotId())); // or before publishing: WapUtil.validateWapPublish(table.ops() != null ? ops.current() : null, stagedId); // throws early if already published
Try / catch
try {
WapUtil.validateWapPublish(ops.current(), stagedSnapshotId);
table.publishWap();
} catch (DuplicateWAPCommitException e) {
log.info("WAP {} already published, skipping", e.wapId());
} Prevention
- Generate a unique wap.id per run instead of reusing fixed ids
- Make publish steps idempotent by catching DuplicateWAPCommitException as success
- Coordinate single ownership of publish jobs per wap.id
- Re-check validateWapPublish on retries after partial failures
When it happens
Trigger: Calling table.fastAppend()/cherryPick flow with WAP enabled (write.wap.id set) and then publishWap for a snapshot whose wap.id matches an already-published ancestor — e.g. publishing the same staged snapshot twice, or re-running a publish job after a partial failure.
Common situations: Idempotent job runners retrying a publish step that already succeeded; two concurrent jobs publishing the same staged WAP snapshot; reusing a wap.id string across runs instead of generating unique ids per run.
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 apply non-unique WAP ID. Found multiple snapshots…
- Cannot apply non-unique WAP ID. Found multiple snapshots…
- Cannot apply non-unique WAP ID. Found multiple snapshots…
- Cannot apply non-unique WAP ID. Found multiple snapshots…
- Cannot apply unknown WAP ID
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/08500d9c3fffa91e.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/apache/iceberg/util/WapUtil.java:54
public static String publishedWapId(Snapshot snapshot) {
return snapshot.summary() != null
? snapshot.summary().get(SnapshotSummary.PUBLISHED_WAP_ID_PROP)
: null;
}
/**
* Check if a given staged snapshot's associated wap-id was already published. Does not fail for
* non-WAP workflows.
*
* @param current the current {@link TableMetadata metadata} for the target table
* @param wapSnapshotId a snapshot id which could have been staged and is associated with a wap id
* @return the WAP ID that will be published, if the snapshot has one
*/
public static String validateWapPublish(TableMetadata current, long wapSnapshotId) {
Snapshot cherryPickSnapshot = current.snapshot(wapSnapshotId);
String wapId = stagedWapId(cherryPickSnapshot);
if (wapId != null && !wapId.isEmpty() && WapUtil.isWapIdPublished(current, wapId)) {
throw new DuplicateWAPCommitException(wapId);
}
return wapId;
}
private static boolean isWapIdPublished(TableMetadata current, String wapId) {
for (long ancestorId : SnapshotUtil.ancestorIds(current.currentSnapshot(), current::snapshot)) {
Snapshot snapshot = current.snapshot(ancestorId);
if (wapId.equals(stagedWapId(snapshot)) || wapId.equals(publishedWapId(snapshot))) {
return true;
}
}
return false;
}
}
View on GitHub (pinned to 86d9c8fc54)