apache/iceberg · error · UnsupportedOperationException

Deleted rows scan task is not supported yet

Error message

Deleted rows scan task is not supported yet

What it means

ChangelogRowReader.openChangelogScanTask explicitly refuses DeletedRowsScanTask with an UnsupportedOperationException. Deleted-row tasks represent per-row deletes from equality deletes that this changelog reader does not implement; it supports added rows and deleted data files instead.

Source

Thrown at spark/v4.2/spark/src/main/java/org/apache/iceberg/spark/source/ChangelogRowReader.java:104

    return cdcRows.iterator();
  }

  private static InternalRow changelogMetadata(ChangelogScanTask task) {
    InternalRow metadataRow = new GenericInternalRow(3);

    metadataRow.update(0, UTF8String.fromString(task.operation().name()));
    metadataRow.update(1, task.changeOrdinal());
    metadataRow.update(2, task.commitSnapshotId());

    return metadataRow;
  }

  private CloseableIterable<InternalRow> openChangelogScanTask(ChangelogScanTask task) {
    if (task instanceof AddedRowsScanTask) {
      return openAddedRowsScanTask((AddedRowsScanTask) task);

    } else if (task instanceof DeletedRowsScanTask) {
      throw new UnsupportedOperationException("Deleted rows scan task is not supported yet");

    } else if (task instanceof DeletedDataFileScanTask) {
      return openDeletedDataFileScanTask((DeletedDataFileScanTask) task);

    } else {
      throw new IllegalArgumentException(
          "Unsupported changelog scan task type: " + task.getClass().getName());
    }
  }

  CloseableIterable<InternalRow> openAddedRowsScanTask(AddedRowsScanTask task) {
    String filePath = task.file().location();
    SparkDeleteFilter deletes = new SparkDeleteFilter(filePath, task.deletes(), counter(), true);
    return deletes.filter(rows(task, deletes.requiredSchema()));
  }

  private CloseableIterable<InternalRow> openDeletedDataFileScanTask(DeletedDataFileScanTask task) {
    String filePath = task.file().location();

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Read the table in delete-only mode (read.stream with delete mode) instead of changelog mode
  2. Rewrite deletes as copy-on-write (delete.mode=copy-on-write) so deletions produce deleted data files the reader supports
  3. Rewrite manifests/data files to convert equality deletes into positional deletes or rewritten files
  4. Upgrade Iceberg if a newer version adds DeletedRowsScanTask support

Example fix

// before
spark.readStream.option("streaming-changelog-enabled", "true").table("t")
// after
spark.readStream.option("streaming-delete-handling-mode", "delete").table("t")
Defensive patterns

Strategy: fallback

Validate before calling

if (task instanceof DeletedRowsScanTask) { /* switch to delete-only mode or rewrite deletes */ }

Type guard

boolean supported(ChangelogScanTask t) { return t instanceof AddedRowsScanTask || t instanceof DeletedDataFileScanTask; }

Try / catch

try { open(task) } catch (UnsupportedOperationException e) { fall back to delete-only read mode }

Prevention

When it happens

Trigger: A streaming changelog scan plan yields a DeletedRowsScanTask (rows deleted via equality deletes without a deleted data file) and the reader attempts to open it.

Common situations: Reading a v2 table's changelog where deletes were expressed as equality deletes (e.g. MERGE INTO / DELETE with an equality predicate); DELETE-only mode streaming without changelog support configured.

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/0537fcaebda05e5c. Report an issue: GitHub.