{"record":{"id":"4b025422df6ca2dc","repo":"apache/iceberg","slug":"cannot-apply-non-unique-wap-id-found-multiple-sna","errorCode":null,"errorMessage":"Cannot apply non-unique WAP ID. Found multiple snapshots with WAP ID '${wapId}'","messagePattern":"Cannot apply non-unique WAP ID\\. Found multiple snapshots with WAP ID '(.+?)'","errorType":"validation","errorClass":"ValidationException","httpStatus":null,"severity":"error","filePath":"spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/procedures/PublishChangesProcedure.java","lineNumber":97,"sourceCode":"  public StructType outputType() {\n    return OUTPUT_TYPE;\n  }\n\n  @Override\n  public InternalRow[] call(InternalRow args) {\n    ProcedureInput input = new ProcedureInput(spark(), tableCatalog(), PARAMETERS, args);\n\n    Identifier tableIdent = input.ident(TABLE_PARAM);\n    String wapId = input.asString(WAP_ID_PARAM);\n\n    return modifyIcebergTable(\n        tableIdent,\n        table -> {\n          Snapshot matchingSnap = null;\n          for (Snapshot snap : table.snapshots()) {\n            if (wapId.equals(WapUtil.stagedWapId(snap))) {\n              if (matchingSnap != null) {\n                throw new ValidationException(\n                    \"Cannot apply non-unique WAP ID. Found multiple snapshots with WAP ID '%s'\",\n                    wapId);\n              } else {\n                matchingSnap = snap;\n              }\n            }\n          }\n\n          if (matchingSnap == null) {\n            throw new ValidationException(\"Cannot apply unknown WAP ID '%s'\", wapId);\n          }\n\n          long wapSnapshotId = matchingSnap.snapshotId();\n          table.manageSnapshots().cherrypick(wapSnapshotId).commit();\n          Snapshot currentSnapshot = table.currentSnapshot();\n          InternalRow outputRow = newInternalRow(wapSnapshotId, currentSnapshot.snapshotId());\n          return new InternalRow[] {outputRow};\n        });","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/procedures/PublishChangesProcedure.java#L79-L115","documentation":"The publish_changes procedure cherry-picks a staged (WAP) snapshot identified by its snap.wap-id property. Publishing must be deterministic, so if more than one snapshot carries the same wap-id, ValidationException is thrown because it would be ambiguous which staged snapshot to publish.","triggerScenarios":"Two writes staged snapshots with the same `spark.wap.id` into the same table, then `CALL cat.system.publish_changes(wap_id => 'dup-id')`.","commonSituations":"Reusing a fixed WAP ID from config across runs; concurrent CI pipelines sharing a WAP ID; missing per-job UUID generation.","solutions":["Generate a unique spark.wap.id per write job (e.g. UUID.randomUUID()) before staging.","Inspect tbl.snapshots for duplicate wap-id values and expire stale staged snapshots.","Re-run publish only after a single snapshot holds the target wap-id.","Enforce in orchestration that concurrent writers never share WAP IDs."],"exampleFix":"// before\nspark.conf.set(\"spark.wap.id\", \"fixed-id\")\n// after\nspark.conf.set(\"spark.wap.id\", java.util.UUID.randomUUID().toString())","handlingStrategy":"validation","validationCode":"long matches = spark.read().format(\"iceberg\").load(\"cat.db.t.snapshots\")\n    .filter(\"snapshot_props['wap-id'] = '\" + wapId + \"'\").count();\nif (matches > 1) throw new IllegalStateException(\"Non-unique WAP ID: \" + wapId);","typeGuard":null,"tryCatchPattern":"try { spark.sql(\"CALL cat.system.publish_changes(wap_id => '\" + wapId + \"')\"); } catch (ValidationException e) { if (e.getMessage().contains(\"non-unique WAP ID\")) { /* deduplicate staged snapshots or use a fresh unique WAP ID */ } throw e; }","preventionTips":["Generate a unique spark.wap.id (UUID) per write job.","Never hardcode or share WAP IDs across concurrent jobs.","Expire stale staged snapshots before republishing.","Audit duplicate wap-id values in the snapshots metadata table."],"tags":["spark","procedures","wap","snapshot","conflict"],"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-14T16:17:12.679Z"}