{"record":{"id":"8166dbdaa263114f","repo":"apache/iceberg","slug":"structinternalrow-is-read-only-8166db","errorCode":null,"errorMessage":"StructInternalRow is read-only","messagePattern":"StructInternalRow is read-only","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"spark/v4.2/spark/src/main/java/org/apache/iceberg/spark/source/StructInternalRow.java","lineNumber":98,"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":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.2/spark/src/main/java/org/apache/iceberg/spark/source/StructInternalRow.java#L80-L116","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nrow.setNullAt(2);\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(2);","handlingStrategy":"type-guard","validationCode":"// Java\nif (row instanceof StructInternalRow) {\n  throw new IllegalStateException(\"Do not mutate scan-output rows; copy first\");\n}","typeGuard":"boolean isMutableRow(InternalRow row) {\n  return !(row instanceof StructInternalRow);\n}","tryCatchPattern":"try {\n  row.setNullAt(i);\n} catch (UnsupportedOperationException e) {\n  if (\"StructInternalRow is read-only\".equals(e.getMessage())) {\n    // copy to a mutable row and retry\n  }\n  throw e;\n}","preventionTips":["Treat rows from Iceberg scans as immutable","Copy into GenericInternalRow before mutation","Never reuse scan rows as aggregation buffers"],"tags":["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"}