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
Even in the file-scoped (rewritable deletes) delta writer, updates cannot be applied in place; Iceberg's Morpheus-style position delta protocol represents an update strictly as a delete plus an insert. The writer throws UnsupportedOperationException to enforce this contract.
Source
Thrown at spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/source/SparkPositionDeltaWrite.java:744
}
private static class UnpartitionedDeltaWriter extends DeleteAndDataDeltaWriter {
private final PartitionSpec dataSpec;
UnpartitionedDeltaWriter(
Table table,
Map<String, DeleteFileSet> rewritableDeletes,
SparkFileWriterFactory writerFactory,
OutputFileFactory dataFileFactory,
OutputFileFactory deleteFileFactory,
Context context) {
super(table, rewritableDeletes, writerFactory, dataFileFactory, deleteFileFactory, 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 {
delegate.insert(row, dataSpec, null);
}
}
private static class PartitionedDeltaWriter extends DeleteAndDataDeltaWriter {
private final PartitionSpec dataSpec;
private final PartitionKey dataPartitionKey;
private final InternalRowWrapper internalRowDataWrapper;
PartitionedDeltaWriter(
Table table,
Map<String, DeleteFileSet> rewritableDeletes,
SparkFileWriterFactory writerFactory,
OutputFileFactory dataFileFactory,View on GitHub (pinned to 86d9c8fc54)
Solutions
- Configure the write so updates are decomposed into delete+insert (this is the required representation).
- Set the table property write.update.mode=copy-on-write (or merge mode) so the engine rewrites whole files for updates.
- Check the Spark/Iceberg version pairing: older integrations did not lower updates for this writer; upgrade if updates must flow through.
Example fix
// before
MERGE INTO t ... WHEN MATCHED THEN UPDATE SET ... // direct update op
// after
ALTER TABLE t SET TBLPROPERTIES ('write.update.mode'='copy-on-write') Defensive patterns
Strategy: validation
Validate before calling
// verify update lowering before writing
String updateMode = table.properties().getOrDefault("write.update.mode", "merge-on-read");
if (!"copy-on-write".equals(updateMode)) { planUpdatesAsDeleteAndInsert(); } Try / catch
try { writer.update(meta, id, row); } catch (UnsupportedOperationException e) { writer.delete(meta, id); writer.insert(row); } Prevention
- Always decompose updates into delete+insert for merge-on-read v2 tables
- Set write.update.mode=copy-on-write if direct update semantics are needed
- Keep Spark and Iceberg versions aligned on delta write capabilities
When it happens
Trigger: A MERGE/UPDATE operation emitting update rows (meta, id, row) to V2FileWriter/PositionDeltaWrite path where update ops reach this writer instead of separate delete+insert ops.
Common situations: Running UPDATE or MERGE WHEN MATCHED UPDATE on a v2 table using merge-on-read with the position delta writer, without the engine lowering updates into delete+insert pairs.
Related errors
- ${this.getClass().getName()} does not implement update
- ${this.getClass().getName()} does not implement insert
- ${this.getClass().getName()} does not implement update
- ${this.getClass().getName()} does not implement insert
- Cannot apply unknown table change: ${change}
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/79356ea367ac90eb.
Report an issue: GitHub.