apache/iceberg · error · UnsupportedOperationException

Update must be represented as delete and insert

Error message

Update must be represented as delete and insert

What it means

The copy-on-write (delta data) writer of SparkPositionDeltaWrite represents every UPDATE as a delete of the old row plus an insert of the new row, per Delta protocol semantics. It throws UnsupportedOperationException from update() because in-place updates are never sent to this writer. If you see it, the execution layer tried an operation the Iceberg Spark integration cannot express.

Source

Thrown at spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/source/SparkPositionDeltaWrite.java:797

        SparkFileWriterFactory writerFactory,
        OutputFileFactory dataFileFactory,
        OutputFileFactory deleteFileFactory,
        Function<InternalRow, InternalRow> rowLineageFromMetadata,
        Context context) {
      super(
          table,
          rewritableDeletes,
          writerFactory,
          dataFileFactory,
          deleteFileFactory,
          rowLineageFromMetadata,
          context);
      this.dataSpec = table.spec();
    }

    @Override
    public void update(InternalRow meta, InternalRow id, InternalRow row) throws IOException {
      throw new UnsupportedOperationException("Update must be represented as delete and insert");
    }

    @Override
    public void insert(InternalRow row) throws IOException {
      reinsert(null, row);
    }

    @Override
    public void reinsert(InternalRow meta, InternalRow row) throws IOException {
      delegate.insert(decorateWithRowLineage(meta, row), dataSpec, null);
    }
  }

  private static class PartitionedDeltaWriter extends DeleteAndDataDeltaWriter {
    private final PartitionSpec dataSpec;
    private final PartitionKey dataPartitionKey;
    private final InternalRowWrapper internalRowDataWrapper;

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Use a Spark version compatible with the iceberg-spark 4.0 module so updates are split into delete+insert.
  2. Rewrite the command (MERGE/UPDATE) so it produces delete+insert, or run with the expected Spark Delta execution path.
  3. If working around, implement a custom RowLevelOperation whose writer translates update into delete+reinsert.
Defensive patterns

Strategy: try-catch

Validate before calling

Preconditions.checkArgument(command instanceof Delete || command instanceof Update || command instanceof Merge, "Only DELETE/UPDATE/MERGE are supported");

Try / catch

try { writer.update(meta, id, row); } catch (UnsupportedOperationException e) {
  // rewrite operation as delete + insert
  writer.delete(meta, id, oldRow); writer.insert(newRow);
}

Prevention

When it happens

Trigger: Spark Delta batch execution invokes update(meta, id, row) on the copy-on-write SparkPositionDeltaWrite instead of decomposing the update into delete+insert (e.g. a Spark version or custom plan that skips the split).

Common situations: Version mismatch between Spark's row-level operation APIs and the pinned iceberg-spark v4.0 module; custom physical plans calling update directly.

Related errors


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