apache/iceberg · error · UnsupportedOperationException

Cannot set row kind in the RowDataProjection

Error message

Cannot set row kind in the RowDataProjection

What it means

RowDataProjection is a read-only projecting wrapper around another RowData: it maps a projected Iceberg schema onto the wrapped row. RowKind mutation is not meaningful for a projection, so setRowKind unconditionally throws UnsupportedOperationException. Reading the kind (getRowKind) delegates to the wrapped row, but writing it is rejected.

Solutions

  1. Mutate the RowKind on the underlying source RowData before wrapping it with RowDataProjection.
  2. Copy the projected values into a mutable row (e.g. GenericRowData) first, then call setRowKind on that copy.
  3. Construct the projection from a wrapped row that already carries the correct RowKind; getRowKind() passes it through.
  4. If you own the code, replace RowDataProjection with a wrapper that delegates setRowKind to the inner row instead of throwing.

Example fix

// before
RowData projected = RowDataProjection.create(schema, projectedSchema).wrap(row);
projected.setRowKind(RowKind.DELETE); // throws

// after
GenericRowData mutable = new GenericRowData(projected.getArity());
for (int i = 0; i < projected.getArity(); i++) {
  mutable.setField(i, /* copy field i */ null);
}
mutable.setRowKind(RowKind.DELETE);
Defensive patterns

Strategy: fallback

Validate before calling

if (row instanceof RowDataProjection) {
  throw new IllegalStateException("Mutate the source RowData, not the projection");
}

Type guard

boolean isMutableRow(RowData row) {
  return !(row instanceof RowDataProjection);
}

Try / catch

try {
  projected.setRowKind(RowKind.DELETE);
} catch (UnsupportedOperationException e) {
  // projected wrapper is read-only: copy to GenericRowData and set kind there
  GenericRowData copy = copyIntoGenericRow(projected);
  copy.setRowKind(RowKind.DELETE);
}

Prevention

When it happens

Trigger: Calling setRowKind(...) on a RowDataProjection instance obtained from RowDataProjection.create(...).wrap(row), typically when a Flink operator (e.g. changelog-producing sink or process function) tries to mutate the row kind of a projected row.

Common situations: Piping projected rows into a Flink upsert/changelog sink that calls setRowKind; writing generic RowData-manipulating utility code that assumes all RowData implementations are mutable; wrapping rows then trying to tag them with DELETE/UPDATE_BEFORE kinds.

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/946ec02e7238abfc. Report an issue: GitHub.

Appendix: source

Thrown at flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/data/RowDataProjection.java:198

  private Object getValue(int pos) {
    Preconditions.checkState(rowData != null, "Row data not wrapped");
    return getters[pos].getFieldOrNull(rowData);
  }

  @Override
  public int getArity() {
    return getters.length;
  }

  @Override
  public RowKind getRowKind() {
    Preconditions.checkState(rowData != null, "Row data not wrapped");
    return rowData.getRowKind();
  }

  @Override
  public void setRowKind(RowKind kind) {
    throw new UnsupportedOperationException("Cannot set row kind in the RowDataProjection");
  }

  @Override
  public boolean isNullAt(int pos) {
    return getValue(pos) == null;
  }

  @Override
  public boolean getBoolean(int pos) {
    return (boolean) getValue(pos);
  }

  @Override
  public byte getByte(int pos) {
    return (byte) getValue(pos);
  }

  @Override

View on GitHub (pinned to 86d9c8fc54)