{"record":{"id":"08500d9c3fffa91e","repo":"apache/iceberg","slug":"duplicatewapcommitexception-wapid","errorCode":null,"errorMessage":"DuplicateWAPCommitException(wapId)","messagePattern":"DuplicateWAPCommitException\\(wapId\\)","errorType":"exception","errorClass":"DuplicateWAPCommitException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/util/WapUtil.java","lineNumber":54,"sourceCode":"  public static String publishedWapId(Snapshot snapshot) {\n    return snapshot.summary() != null\n        ? snapshot.summary().get(SnapshotSummary.PUBLISHED_WAP_ID_PROP)\n        : null;\n  }\n\n  /**\n   * Check if a given staged snapshot's associated wap-id was already published. Does not fail for\n   * non-WAP workflows.\n   *\n   * @param current the current {@link TableMetadata metadata} for the target table\n   * @param wapSnapshotId a snapshot id which could have been staged and is associated with a wap id\n   * @return the WAP ID that will be published, if the snapshot has one\n   */\n  public static String validateWapPublish(TableMetadata current, long wapSnapshotId) {\n    Snapshot cherryPickSnapshot = current.snapshot(wapSnapshotId);\n    String wapId = stagedWapId(cherryPickSnapshot);\n    if (wapId != null && !wapId.isEmpty() && WapUtil.isWapIdPublished(current, wapId)) {\n      throw new DuplicateWAPCommitException(wapId);\n    }\n\n    return wapId;\n  }\n\n  private static boolean isWapIdPublished(TableMetadata current, String wapId) {\n    for (long ancestorId : SnapshotUtil.ancestorIds(current.currentSnapshot(), current::snapshot)) {\n      Snapshot snapshot = current.snapshot(ancestorId);\n      if (wapId.equals(stagedWapId(snapshot)) || wapId.equals(publishedWapId(snapshot))) {\n        return true;\n      }\n    }\n    return false;\n  }\n}\n","sourceCodeStart":36,"sourceCodeEnd":70,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/util/WapUtil.java#L36-L70","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\nString wapId = \"daily-ingest\"; // reused every run\ntable.publishWap();\n// after\nString wapId = UUID.randomUUID().toString();\ntable.properties().put(TableProperties.WRITE_WAP_ID, wapId);\n// ... fastAppend ...\nWapUtil.validateWapPublish(ops.refresh().current(), stagedId); // throws if already published\ntable.publishWap();","handlingStrategy":"try-catch","validationCode":"String wapId = WapUtil.stagedWapId(table.currentSnapshot().snapshot(table.currentSnapshot().snapshotId()));\n// or before publishing:\nWapUtil.validateWapPublish(table.ops() != null ? ops.current() : null, stagedId); // throws early if already published","typeGuard":null,"tryCatchPattern":"try {\n  WapUtil.validateWapPublish(ops.current(), stagedSnapshotId);\n  table.publishWap();\n} catch (DuplicateWAPCommitException e) {\n  log.info(\"WAP {} already published, skipping\", e.wapId());\n}","preventionTips":["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"],"tags":["wap","idempotency","snapshots"],"backgroundTag":"invalid-state-transition","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}