{"record":{"id":"222bb2aa4e26040d","repo":"apache/iceberg","slug":"structinternalrow-is-read-only-222bb2","errorCode":null,"errorMessage":"StructInternalRow is read-only","messagePattern":"StructInternalRow is read-only","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/source/StructInternalRow.java","lineNumber":95,"sourceCode":"\n  private StructInternalRow(Types.StructType type, StructLike struct) {\n    this.type = type;\n    this.struct = struct;\n  }\n\n  public StructInternalRow setStruct(StructLike newStruct) {\n    this.struct = newStruct;\n    return this;\n  }\n\n  @Override\n  public int numFields() {\n    return struct.size();\n  }\n\n  @Override\n  public void setNullAt(int i) {\n    throw new UnsupportedOperationException(\"StructInternalRow is read-only\");\n  }\n\n  @Override\n  public void update(int i, Object value) {\n    throw new UnsupportedOperationException(\"StructInternalRow is read-only\");\n  }\n\n  @Override\n  public InternalRow copy() {\n    return this;\n  }\n\n  @Override\n  public boolean isNullAt(int ordinal) {\n    return struct.get(ordinal, Object.class) == null;\n  }\n\n  @Override","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/source/StructInternalRow.java#L77-L113","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nrow.setNullAt(2); // UnsupportedOperationException\n// after\nUnsafeRow copied = new UnsafeRow(row.numFields());\nUnsafeRowWriter writer = new UnsafeRowWriter(copied, ...);\nwriter.setNullAt(2);","handlingStrategy":"type-guard","validationCode":"if (row instanceof StructInternalRow) { /* read-only: never mutate in place */ }","typeGuard":"boolean isMutable(InternalRow row) { return !(row instanceof StructInternalRow); }","tryCatchPattern":"try { row.setNullAt(i); } catch (UnsupportedOperationException e) { /* materialize a writable copy and retry */ }","preventionTips":["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"],"tags":["spark","internalrow","read-only","unsupported"],"backgroundTag":"unsupported-operation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}