apache/iceberg · error · UnsupportedOperationException
StructInternalRow is read-only
Error message
StructInternalRow is read-only
What it means
StructInternalRow is a read-only Spark InternalRow view over an Iceberg StructLike value. setNullAt is part of the writable InternalRow API but is intentionally unsupported; calling it always throws UnsupportedOperationException.
Source
Thrown at spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/source/StructInternalRow.java:93
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; copy into a mutable row first (e.g. new GenericInternalRow or SpecificInternalRow)
- If mutation is needed for downstream Spark code, wrap with a copy that supports updates
- Check the data flow so read-only Iceberg rows are only used for reads
Example fix
// before
row.setNullAt(ordinal); // StructInternalRow
// after
GenericInternalRow mutable = new GenericInternalRow(row.numFields());
for (int i = 0; i < row.numFields(); i++) { mutable.update(i, row.get(i, null)); }
mutable.setNullAt(ordinal); Defensive patterns
Strategy: type-guard
Validate before calling
if (row instanceof StructInternalRow) {
throw new UnsupportedOperationException("Cannot mutate a read-only StructInternalRow; copy first");
} Type guard
boolean isMutableRow(org.apache.spark.sql.catalyst.InternalRow row) {
return !(row instanceof StructInternalRow);
} Try / catch
try {
row.setNullAt(i);
} catch (UnsupportedOperationException e) {
row = copyToMutableRow(row);
row.setNullAt(i);
} Prevention
- Treat StructInternalRow as strictly read-only; never pass it where mutation is expected
- Remember copy() returns the same instance — build a GenericInternalRow for mutable copies
- Keep Iceberg rows on the read side of pipelines only
When it happens
Trigger: Calling setNullAt on a StructInternalRow, typically from Spark code that assumes the row is mutable (e.g. row-based writers, aggregation buffers, or generic InternalRow mutation utilities).
Common situations: Passing a StructInternalRow into Spark operators that mutate rows in place; using it as an accumulator or buffer row; copy() returns this (same instance), so downstream mutation attempts hit this throw.
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
- Not implemented: set
- StructInternalRow is read-only
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/c123a6cdc7ee5811.
Report an issue: GitHub.