{"record":{"id":"c123a6cdc7ee5811","repo":"apache/iceberg","slug":"structinternalrow-is-read-only-c123a6","errorCode":null,"errorMessage":"StructInternalRow is read-only","messagePattern":"StructInternalRow is read-only","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/source/StructInternalRow.java","lineNumber":93,"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":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/source/StructInternalRow.java#L75-L111","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nrow.setNullAt(ordinal); // StructInternalRow\n// after\nGenericInternalRow mutable = new GenericInternalRow(row.numFields());\nfor (int i = 0; i < row.numFields(); i++) { mutable.update(i, row.get(i, null)); }\nmutable.setNullAt(ordinal);","handlingStrategy":"type-guard","validationCode":"if (row instanceof StructInternalRow) {\n  throw new UnsupportedOperationException(\"Cannot mutate a read-only StructInternalRow; copy first\");\n}","typeGuard":"boolean isMutableRow(org.apache.spark.sql.catalyst.InternalRow row) {\n  return !(row instanceof StructInternalRow);\n}","tryCatchPattern":"try {\n  row.setNullAt(i);\n} catch (UnsupportedOperationException e) {\n  row = copyToMutableRow(row);\n  row.setNullAt(i);\n}","preventionTips":["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"],"tags":["unsupported-operation","spark","read-only","internal-row"],"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"}