{"record":{"id":"500faedb451316dc","repo":"apache/iceberg","slug":"getclass-getname-does-not-support-merge","errorCode":null,"errorMessage":"${getClass().getName()} does not support merge","messagePattern":"(.+?) does not support merge","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/deletes/PositionDeleteIndex.java","lineNumber":50,"sourceCode":"   */\n  void delete(long position);\n\n  /**\n   * Set a range of deleted row positions.\n   *\n   * @param posStart inclusive beginning of position range\n   * @param posEnd exclusive ending of position range\n   */\n  void delete(long posStart, long posEnd);\n\n  /**\n   * Adds positions from the other index, modifying this index in place.\n   *\n   * @param that the other index to merge\n   */\n  default void merge(PositionDeleteIndex that) {\n    if (!that.deleteFiles().isEmpty()) {\n      throw new UnsupportedOperationException(getClass().getName() + \" does not support merge\");\n    }\n    that.forEach(this::delete);\n  }\n\n  /**\n   * Checks whether a row at the position is deleted.\n   *\n   * @param position deleted row position\n   * @return whether the position is deleted\n   */\n  boolean isDeleted(long position);\n\n  /** Returns true if this collection contains no element. */\n  boolean isEmpty();\n\n  /** Returns true if this collection contains elements. */\n  default boolean isNotEmpty() {\n    return !isEmpty();","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/deletes/PositionDeleteIndex.java#L32-L68","documentation":"PositionDeleteIndex.merge is a default method that copies positions from another index by iterating that.forEach(this::delete). The guard throws UnsupportedOperationException if the incoming index has delete files but this index's implementation doesn't override merge - i.e. the class cannot efficiently (or at all) absorb another index, so mutating it delete-by-delete would be incorrect or unsafe (e.g. delete-file bookkeeping would be lost).","triggerScenarios":"Calling index.merge(other) where other.deleteFiles() is non-empty and index's concrete class (e.g. BitmapPositionDeleteIndex variants without an override, or a wrapper/DV-backed index) does not implement merge. Reachable from readAndMergeDVs, writer close, and writeDeletes paths.","commonSituations":"Deletion-vector read paths merging DV indexes with position indexes; combining multiple delete-file indexes during scan planning; custom PositionDeleteIndex implementations that forgot to override merge().","solutions":["Implement merge() in the custom PositionDeleteIndex to absorb positions and delete-file metadata","Use an index implementation that supports merge (e.g. BitmapPositionDeleteIndex with merge support)","Avoid merging indexes with non-empty deleteFiles into an unsupported receiver; instead rebuild the receiver's index from the other's positions","Upgrade Iceberg version if hitting this from built-in DV/read paths - support for merge in more implementations was added over time"],"exampleFix":"// before\n@Override\npublic void merge(PositionDeleteIndex that) { /* not overridden -> default throws */ }\n// after\n@Override\npublic void merge(PositionDeleteIndex that) {\n  if (that instanceof MyIndex) {\n    this.deleted.union(((MyIndex) that).deleted);\n    this.deleteFiles.addAll(that.deleteFiles());\n  } else {\n    that.forEach(this::delete);\n    this.deleteFiles.addAll(that.deleteFiles());\n  }\n}","handlingStrategy":"validation","validationCode":"if (!other.deleteFiles().isEmpty() && !supportsMerge(index)) {\n  throw new IllegalStateException(\"Receiver index does not support merge with delete files\");\n}\nboolean supportsMerge(PositionDeleteIndex idx) {\n  return !(idx.getClass().equals(PositionDeleteIndex.class)) &&\n    java.lang.reflect.Array.get(new BitmapPositionDeleteIndex[0], 0) != null; // or instanceof a merge-capable type\n}","typeGuard":"boolean mergeCapable(PositionDeleteIndex idx) {\n  // check the concrete class overrides merge, e.g. BitsetMergeIndex / DV-capable types\n  try {\n    idx.getClass().getMethod(\"merge\", PositionDeleteIndex.class);\n    return idx.getClass().getMethod(\"merge\", PositionDeleteIndex.class).getDeclaringClass() != PositionDeleteIndex.class;\n  } catch (NoSuchMethodException e) { return false; }\n}","tryCatchPattern":"try {\n  index.merge(other);\n} catch (UnsupportedOperationException e) {\n  PositionDeleteIndex merged = new BitmapPositionDeleteIndex();\n  other.forEach(merged::delete);\n  index.forEach(merged::delete);\n}","preventionTips":["Custom PositionDeleteIndex implementations should override merge() when they track delete files","Only merge into indexes known to support absorbing other indexes' delete-file metadata","Check other.deleteFiles().isEmpty() before merging into a default-implementation receiver","Prefer built-in merge-capable implementations on recent Iceberg versions for DV read paths"],"tags":["unsupported-operation","position-delete","merge"],"backgroundTag":"unsupported-operation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}