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
- 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
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
- 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
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
- Cannot modify
- Cannot modify ${getClass().getName()}
- ${getClass().getName()} does not implement length
- ${this.getClass().getName()} does not implement update
- this.getClass().getName() + " does not implement insert"
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/500faedb451316dc.
Report an issue: GitHub.