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;
  }

  @Override

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Replace the empty index with a real mutable index, e.g. new BitmapPositionDeleteIndex(), before recording positions
  2. Never cache or reuse EmptyPositionDeleteIndex.get() as a write target
  3. If merging, use an implementation that supports merge() instead of writing into the singleton
  4. 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

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


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