apache/iceberg · error · UnsupportedOperationException

${getClass().getName()} does not support merge

Error message

${getClass().getName()} does not support merge

What it means

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).

Source

Thrown at core/src/main/java/org/apache/iceberg/deletes/PositionDeleteIndex.java:50

   */
  void delete(long position);

  /**
   * Set a range of deleted row positions.
   *
   * @param posStart inclusive beginning of position range
   * @param posEnd exclusive ending of position range
   */
  void delete(long posStart, long posEnd);

  /**
   * Adds positions from the other index, modifying this index in place.
   *
   * @param that the other index to merge
   */
  default void merge(PositionDeleteIndex that) {
    if (!that.deleteFiles().isEmpty()) {
      throw new UnsupportedOperationException(getClass().getName() + " does not support merge");
    }
    that.forEach(this::delete);
  }

  /**
   * Checks whether a row at the position is deleted.
   *
   * @param position deleted row position
   * @return whether the position is deleted
   */
  boolean isDeleted(long position);

  /** Returns true if this collection contains no element. */
  boolean isEmpty();

  /** Returns true if this collection contains elements. */
  default boolean isNotEmpty() {
    return !isEmpty();

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Implement merge() in the custom PositionDeleteIndex to absorb positions and delete-file metadata
  2. Use an index implementation that supports merge (e.g. BitmapPositionDeleteIndex with merge support)
  3. Avoid merging indexes with non-empty deleteFiles into an unsupported receiver; instead rebuild the receiver's index from the other's positions
  4. Upgrade Iceberg version if hitting this from built-in DV/read paths - support for merge in more implementations was added over time

Example fix

// before
@Override
public void merge(PositionDeleteIndex that) { /* not overridden -> default throws */ }
// after
@Override
public void merge(PositionDeleteIndex that) {
  if (that instanceof MyIndex) {
    this.deleted.union(((MyIndex) that).deleted);
    this.deleteFiles.addAll(that.deleteFiles());
  } else {
    that.forEach(this::delete);
    this.deleteFiles.addAll(that.deleteFiles());
  }
}
Defensive patterns

Strategy: validation

Validate before calling

if (!other.deleteFiles().isEmpty() && !supportsMerge(index)) {
  throw new IllegalStateException("Receiver index does not support merge with delete files");
}
boolean supportsMerge(PositionDeleteIndex idx) {
  return !(idx.getClass().equals(PositionDeleteIndex.class)) &&
    java.lang.reflect.Array.get(new BitmapPositionDeleteIndex[0], 0) != null; // or instanceof a merge-capable type
}

Type guard

boolean mergeCapable(PositionDeleteIndex idx) {
  // check the concrete class overrides merge, e.g. BitsetMergeIndex / DV-capable types
  try {
    idx.getClass().getMethod("merge", PositionDeleteIndex.class);
    return idx.getClass().getMethod("merge", PositionDeleteIndex.class).getDeclaringClass() != PositionDeleteIndex.class;
  } catch (NoSuchMethodException e) { return false; }
}

Try / catch

try {
  index.merge(other);
} catch (UnsupportedOperationException e) {
  PositionDeleteIndex merged = new BitmapPositionDeleteIndex();
  other.forEach(merged::delete);
  index.forEach(merged::delete);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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().

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/500faedb451316dc. Report an issue: GitHub.