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 mutable row API but is intentionally unsupported, so it always throws UnsupportedOperationException.
Source
Thrown at spark/v4.2/spark/src/main/java/org/apache/iceberg/spark/source/StructInternalRow.java:98
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 row; create a mutable copy (e.g. new GenericInternalRow or row.copy() into a mutable buffer) before modifying
- If mutation is required by Spark internals, wrap the value in a mutable InternalRow implementation instead of StructInternalRow
- Refactor code that assumes mutability to treat scan output rows as immutable
Example fix
// before row.setNullAt(2); // after GenericInternalRow mutable = new GenericInternalRow(row.numFields()); for (int i = 0; i < row.numFields(); i++) mutable.update(i, row.get(i, null)); mutable.setNullAt(2);
Defensive patterns
Strategy: type-guard
Validate before calling
// Java
if (row instanceof StructInternalRow) {
throw new IllegalStateException("Do not mutate scan-output rows; copy first");
} Type guard
boolean isMutableRow(InternalRow row) {
return !(row instanceof StructInternalRow);
} Try / catch
try {
row.setNullAt(i);
} catch (UnsupportedOperationException e) {
if ("StructInternalRow is read-only".equals(e.getMessage())) {
// copy to a mutable row and retry
}
throw e;
} Prevention
- Treat rows from Iceberg scans as immutable
- Copy into GenericInternalRow before mutation
- Never reuse scan rows as aggregation buffers
When it happens
Trigger: Calling setNullAt(int) on a StructInternalRow, typically from Spark code that assumes rows returned from a scan are mutable (e.g. custom aggregation buffers, unsafe row conversion code that mutates in place).
Common situations: Custom Spark expressions or UDAFs that try to mutate input rows, misusing the row returned by StructInternalRow as a scratch buffer instead of copying it first.
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
- Remove is not supported
- StructInternalRow is read-only
- Remove is not supported
- Unknown type for int field. Type name: + integer.getClass().
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/8166dbdaa263114f.
Report an issue: GitHub.