apache/iceberg · error · UnsupportedOperationException
StructInternalRow is read-only
Error message
StructInternalRow is read-only
What it means
StructInternalRow is an immutable read-only view of an Iceberg StructLike as a Spark InternalRow. setNullAt is part of Spark's mutable-row API and is intentionally unsupported — Iceberg rows are not mutable in place.
Source
Thrown at spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/source/StructInternalRow.java:95
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 the returned InternalRow; call row.copy() semantics are 'this', so build a new UnsafeRow/mutable row and populate it instead
- Wrap the row in a Spark MutableRow / UnsafeRow copy before mutating
- If Spark itself throws this, upgrade Iceberg/Spark — it can indicate an incompatibility where Spark assumes writability it shouldn't
Example fix
// before row.setNullAt(2); // UnsupportedOperationException // after UnsafeRow copied = new UnsafeRow(row.numFields()); UnsafeRowWriter writer = new UnsafeRowWriter(copied, ...); writer.setNullAt(2);
Defensive patterns
Strategy: type-guard
Validate before calling
if (row instanceof StructInternalRow) { /* read-only: never mutate in place */ } Type guard
boolean isMutable(InternalRow row) { return !(row instanceof StructInternalRow); } Try / catch
try { row.setNullAt(i); } catch (UnsupportedOperationException e) { /* materialize a writable copy and retry */ } Prevention
- Treat all Iceberg-produced InternalRows as immutable
- Use UnsafeProjection.apply(row) to obtain a writable copy before mutation
- Never cache and later mutate scan results
When it happens
Trigger: Spark internal code (or user code) calls setNullAt on a row produced by Iceberg's SparkScan, e.g. when Spark's UnsafeRowWriter or a buffer-mutation optimization assumes writability.
Common situations: Spark executors/buffer mutation paths trying to modify scan results; users mistakenly treating IcebergInternalRow output as a writable row in custom executors or UDF plumbing.
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
- StructInternalRow is read-only
- Cannot add column %s since setting default values in Spark i
- Transform is not supported: ${transform}
- Cannot convert unknown expression: ${expr}
- Deleted rows scan task is not supported yet
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/222bb2aa4e26040d.
Report an issue: GitHub.