apache/iceberg · error · UnsupportedOperationException
Cannot modify
Error message
Cannot modify
What it means
EmptyPositionDeleteIndex is an immutable singleton representing an empty position delete index. Calling delete(long) throws UnsupportedOperationException because the singleton must never be mutated; mutation would corrupt shared state used by any reader that received this instance.
Source
Thrown at core/src/main/java/org/apache/iceberg/deletes/EmptyPositionDeleteIndex.java:33
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.iceberg.deletes;
class EmptyPositionDeleteIndex implements PositionDeleteIndex {
private static final EmptyPositionDeleteIndex INSTANCE = new EmptyPositionDeleteIndex();
private EmptyPositionDeleteIndex() {}
static EmptyPositionDeleteIndex get() {
return INSTANCE;
}
@Override
public void delete(long position) {
throw new UnsupportedOperationException("Cannot modify " + getClass().getName());
}
@Override
public void delete(long posStart, long posEnd) {
throw new UnsupportedOperationException("Cannot modify " + getClass().getName());
}
@Override
public boolean isDeleted(long position) {
return false;
}
@Override
public boolean isEmpty() {
return true;
}
@OverrideView on GitHub (pinned to 86d9c8fc54)
Solutions
- Replace the empty index with a real mutable index, e.g. new BitmapPositionDeleteIndex(), before recording positions
- Never cache or reuse EmptyPositionDeleteIndex.get() as a write target
- If merging, use an implementation that supports merge() instead of writing into the singleton
- Audit custom delete-handling code to detect the empty-index singleton before mutation
Example fix
// before
PositionDeleteIndex index = EmptyPositionDeleteIndex.get();
index.delete(position); // UnsupportedOperationException
// after
PositionDeleteIndex index = (existing == null || existing instanceof EmptyPositionDeleteIndex)
? new BitmapPositionDeleteIndex()
: existing;
index.delete(position); Defensive patterns
Strategy: type-guard
Validate before calling
if (index instanceof EmptyPositionDeleteIndex) {
index = new BitmapPositionDeleteIndex();
} Type guard
static boolean isMutableIndex(PositionDeleteIndex idx) {
return !(idx instanceof EmptyPositionDeleteIndex);
} Try / catch
try {
index.delete(pos);
} catch (UnsupportedOperationException e) {
throw new IllegalStateException("Attempted to mutate immutable empty index; use a concrete index", e);
} Prevention
- Treat EmptyPositionDeleteIndex.get() as read-only: only isDeleted/forEach
- Create a mutable index whenever positions will be recorded
- Never pass the singleton to code that writes positions
- Review custom scan pipelines for shared-index reuse
When it happens
Trigger: Calling EmptyPositionDeleteIndex.get().delete(pos) directly, or passing the singleton to code such as PositionDeleteIndex.merge that invokes delete() on it (e.g. DV-based indexes merging into an EmptyPositionDeleteIndex).
Common situations: Custom scan/delete-code paths that assume every PositionDeleteIndex is mutable; constructing read plans for tables with no position deletes then attempting to record new positions into the shared singleton.
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 ${getClass().getName()}
- Cannot set value
- ${getClass().getName()} does not implement length
- ${getClass().getName()} does not support merge
- Not implemented: set
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/d09b38da84794e91.
Report an issue: GitHub.