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;
  }

  @Override

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Do not mutate StructInternalRow; copy into a mutable row first (e.g. new GenericInternalRow or SpecificInternalRow)
  2. If mutation is needed for downstream Spark code, wrap with a copy that supports updates
  3. 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

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


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/c123a6cdc7ee5811. Report an issue: GitHub.