apache/iceberg · error · UnsupportedOperationException

Unknown row kind:

Error message

Unknown row kind: 

What it means

BaseDeltaTaskWriter.write() dispatches RowData based on the row's RowKind: INSERT rows go to the upsert/append writer, and UPDATE_BEFORE/UPDATE_AFTER/DELETE rows route to delete handling. Any other RowKind reaches the default branch, which throws this UnsupportedOperationException because the writer cannot interpret the row's change semantics.

Source

Thrown at flink/v2.1/flink/src/main/java/org/apache/iceberg/flink/sink/BaseDeltaTaskWriter.java:109

        break;

      case UPDATE_BEFORE:
        if (upsert) {
          break; // UPDATE_BEFORE is not necessary for UPSERT, we do nothing to prevent delete one
          // row twice
        }
        writer.delete(row);
        break;
      case DELETE:
        if (upsert) {
          writer.deleteKey(keyProjection.wrap(row));
        } else {
          writer.delete(row);
        }
        break;

      default:
        throw new UnsupportedOperationException("Unknown row kind: " + row.getRowKind());
    }
  }

  protected class RowDataDeltaWriter extends BaseEqualityDeltaWriter {
    RowDataDeltaWriter(PartitionKey partition, PartitioningDVWriter<RowData> dvFileWriter) {
      super(partition, schema, deleteSchema, DeleteGranularity.FILE, dvFileWriter);
    }

    @Override
    protected StructLike asStructLike(RowData data) {
      return wrapper.wrap(data);
    }

    @Override
    protected StructLike asStructLikeKey(RowData data) {
      return keyWrapper.wrap(data);
    }
  }

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Verify the upstream operator emits only valid RowKind values (INSERT, UPDATE_BEFORE, UPDATE_AFTER, DELETE) for CDC streams.
  2. Ensure the sink input is a proper changelog stream (to changelogStream / upsert mode) matching the data's RowKinds.
  3. Align Flink and Iceberg runtime versions so RowKind is interpreted consistently.
  4. Filter or sanitize invalid records upstream before the sink.

Example fix

// before
stream.map(row -> RowDataUtil.setKind(row, (byte) 7));
// after
stream.map(row -> RowDataUtil.setKind(row, RowKind.INSERT));
Defensive patterns

Strategy: validation

Validate before calling

RowKind kind = row.getRowKind();
if (kind != RowKind.INSERT && kind != RowKind.UPDATE_BEFORE && kind != RowKind.UPDATE_AFTER && kind != RowKind.DELETE) {
  throw new IllegalArgumentException("Invalid RowKind before sink: " + kind);
}

Type guard

boolean hasValidRowKind(RowData row) {
  switch (row.getRowKind()) {
    case INSERT:
    case UPDATE_BEFORE:
    case UPDATE_AFTER:
    case DELETE:
      return true;
    default:
      return false;
  }
}

Try / catch

try { sinkWrite(row); } catch (UnsupportedOperationException e) { LOG.error("Row with unhandled RowKind {}; drop or route to DLQ", row.getRowKind(), e); }

Prevention

When it happens

Trigger: Feeding a RowData with an unexpected RowKind into an Iceberg Flink sink (e.g. a corrupted stream record or a new RowKind value from a newer Flink version) via write(), or through equality/position delete writer paths like writeDeleteFile and writePosDeleteFile.

Common situations: Custom Flink operators producing rows with fabricated RowKinds; Flink version upgrades introducing new RowKind constants; malformed CDC streams where a changelog record's kind byte was corrupted in transit.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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