apache/iceberg · error · UnsupportedOperationException
StructInternalRow is read-only
Error message
StructInternalRow is read-only
What it means
StructInternalRow is a read-only wrapper that adapts an Iceberg StructLike into a Spark InternalRow for reading. Spark occasionally calls mutation methods like setNullAt on InternalRow implementations, but this row is produced only for query output and never supports mutation, so any mutation attempt throws UnsupportedOperationException.
Source
Thrown at spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/source/StructInternalRow.java:89
private StructInternalRow(Types.StructType type, StructLike struct) {
this.type = type;
this.struct = struct;
}
public StructInternalRow setStruct(StructLike newStruct) {
this.struct = newStruct;
return this;
}
@Override
public int numFields() {
return struct.size();
}
@Override
public void setNullAt(int i) {
throw new UnsupportedOperationException("StructInternalRow is read-only");
}
@Override
public void update(int i, Object value) {
throw new UnsupportedOperationException("StructInternalRow is read-only");
}
@Override
public InternalRow copy() {
return this;
}
@Override
public boolean isNullAt(int ordinal) {
return struct.get(ordinal, Object.class) == null;
}
@OverrideView on GitHub (pinned to 86d9c8fc54)
Solutions
- Do not mutate StructInternalRow; call copy() (returns a mutable copy via Spark's InternalRow.copy path) before writing, or wrap in an UnsafeRow first
- If writing custom Spark code, copy the row into your own buffer instead of calling setNullAt/update
- If hit inside a connector/plugin, report it — the plugin must not mutate read-path rows
Example fix
// before row.setNullAt(3); // after InternalRow mutable = row.copy(); // via GenericInternalRow conversion mutable.setNullAt(3);
Defensive patterns
Strategy: try-catch
Validate before calling
// treat all scan-produced InternalRows as immutable
if (row instanceof org.apache.iceberg.spark.source.StructInternalRow) {
throw new IllegalArgumentException("StructInternalRow is read-only; copy before mutating");
} Type guard
boolean isReadOnlyRow(InternalRow r) { return r instanceof org.apache.iceberg.spark.source.StructInternalRow; } Try / catch
try {
row.setNullAt(i);
} catch (UnsupportedOperationException e) {
InternalRow copy = row.copy();
copy.setNullAt(i);
} Prevention
- Never mutate rows returned from Iceberg scans
- Call copy() when you need a writable InternalRow
- Assume InternalRow from batch sources may be read-only
When it happens
Trigger: Spark's InternalRow API contract calls setNullAt(i) on this row — e.g. during UnsafeRow conversion, aggregation buffer updates, or any operator that tries to write into the row instead of copying it first.
Common situations: Custom Spark expressions or UDFs that mutate input rows in place; third-party Spark extensions that assume writable rows; using StructInternalRow outside the read path (e.g. as a write buffer).
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
- Remove is not supported
- StructInternalRow is read-only
- Not implemented: set
- StructInternalRow is read-only
- Not implemented: set
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/c74d107112ab88e4.
Report an issue: GitHub.