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 dispatches incremental changelog scan tasks by type. AddedRowsScanTask and DeletedDataFileScanTask are supported, but DeletedRowsScanTask (position-delete-based row deletions carrying prior-row values) has no reader implementation yet, so it throws UnsupportedOperationException. The changelog mode ('incremental' with deletes) cannot process such tasks.

Source

Thrown at spark/v4.1/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. Avoid position deletes on the table: enable copy-on-write (write.delete.mode=copy-on-write / write.merge.mode=copy-on-write) so deletions produce deleted data files instead.
  2. Use a scan mode that does not need to emit prior row values for position deletes, or periodic full re-reads instead of incremental changelog.
  3. Upgrade Iceberg — newer releases may have implemented DeletedRowsScanTask handling.
  4. Run frequent rewrites/compaction to remove delete files before the changelog window.

Example fix

// before (table properties)
"write.delete.mode": "merge-on-read"
// after
"write.delete.mode": "copy-on-write"
Defensive patterns

Strategy: validation

Validate before calling

// detect position-delete usage before enabling changelog reads
boolean usesPositionDeletes = spark.read.format("iceberg")
    .load(table + ".files").filter($"content" === 2).count() > 0;
if (usesPositionDeletes) { /* reconfigure writers or avoid changelog mode */ }

Try / catch

try {
    spark.readStream.format("iceberg").option("stream-from-timestamp", ts).load(table);
} catch (UnsupportedOperationException e) {
    if (e.getMessage().contains("Deleted rows scan task")) { /* switch to copy-on-write or full re-read */ }
}

Prevention

When it happens

Trigger: Streaming/incremental read with read.changelog or scan.mode=incremental-append... when the computed task set contains a DeletedRowsScanTask — i.e. rows removed via v2 position deletes (merge-on-read delete files) between the start and end snapshot.

Common situations: Enabling changelog reads on tables where writers emit position deletes (Spark MERGE/DELETE with copy-on-write off, Flink upsert writes); forgetting that only equality/position delete handling for DeletedDataFile tasks is implemented; running an older Iceberg version where support is narrower.

Related errors


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