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

  @Override

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Do not mutate StructInternalRow; call copy() (returns a mutable copy via Spark's InternalRow.copy path) before writing, or wrap in an UnsafeRow first
  2. If writing custom Spark code, copy the row into your own buffer instead of calling setNullAt/update
  3. 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

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


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