apache/iceberg · error · UnsupportedOperationException

this.getClass().getName() + does not implement update

Error message

this.getClass().getName() +  does not implement update

What it means

The copy-on-write position-delete writer for MERGE/Delta operations only supports writing whole-row deletes identified by file and position. It deliberately throws UnsupportedOperationException from update() because updating a row in place is not representable — Spark Delta semantics require updates to be decomposed into delete+insert before reaching this writer.

Source

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

    @Override
    public void delete(InternalRow metadata, InternalRow id) throws IOException {
      int specId = metadata.getInt(specIdOrdinal);
      PartitionSpec spec = specs.get(specId);

      InternalRow partition = metadata.getStruct(partitionOrdinal, partitionRowWrapper.size());
      StructProjection partitionProjection = partitionProjections.get(specId);
      partitionProjection.wrap(partitionRowWrapper.wrap(partition));

      String file = id.getString(fileOrdinal);
      long position = id.getLong(positionOrdinal);
      positionDelete.set(file, position);
      delegate.write(positionDelete, spec, partitionProjection);
    }

    @Override
    public void update(InternalRow metadata, InternalRow id, InternalRow row) {
      throw new UnsupportedOperationException(
          this.getClass().getName() + " does not implement update");
    }

    @Override
    public void insert(InternalRow row) throws IOException {
      throw new UnsupportedOperationException(
          this.getClass().getName() + " does not implement insert");
    }

    @Override
    public WriterCommitMessage commit() throws IOException {
      close();

      DeleteWriteResult result = delegate.result();
      return new DeltaTaskCommit(result);
    }

    @Override

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Ensure updates are decomposed by Spark into delete+insert (use supported Spark/Iceberg version pairs for the v4.0 module).
  2. Do not call update() directly on this writer; use delete() followed by insert()/reinsert().
  3. If extending the writer, implement update() in your subclass instead of relying on the default.
  4. Check that the command is routed through SparkRowLevelOperationBuilder, which produces the correct operation for the mode.

Example fix

// before: direct update call on the writer
writer.update(metadataRow, idRow, updatedRow);
// after: express as delete + insert
writer.delete(metadataRow, idRow);
writer.insert(updatedRow);
Defensive patterns

Strategy: type-guard

Validate before calling

if (writer instanceof SparkPositionDeltaWrite && supportsUpdate(writer)) {
  writer.update(meta, id, row);
} else {
  writer.delete(meta, id, row);
  writer.insert(row);
}

Type guard

boolean supportsInPlaceUpdate(Object w) { return w instanceof SupportsUpdate; }

Try / catch

try { writer.update(meta, id, row); } catch (UnsupportedOperationException e) {
  writer.delete(meta, id, row); writer.insert(row);
}

Prevention

When it happens

Trigger: A Spark DELETE/MERGE ... UPDATE execution path calls update(metadata, id, row) on the SparkPositionDeltaWrite copy-on-write writer — only possible if a newer Spark Delta API surface routes an update op directly to this writer instead of split delete+insert.

Common situations: Custom or forked Spark integrations invoking the row-level writer API directly; running against a Spark version whose Delta execution calls update() rather than delete(); misuse of the internal writer API in tests/tools.

Related errors


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