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
- Verify the upstream operator emits only valid RowKind values (INSERT, UPDATE_BEFORE, UPDATE_AFTER, DELETE) for CDC streams.
- Ensure the sink input is a proper changelog stream (to changelogStream / upsert mode) matching the data's RowKinds.
- Align Flink and Iceberg runtime versions so RowKind is interpreted consistently.
- 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
- Feed the sink a proper changelog stream (RowKind-bearing) rather than append-only data fabricated with kinds.
- Sanitize or drop rows with non-standard RowKind in an upstream operator.
- Keep Flink and Iceberg versions aligned so RowKind enums match.
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
- Unknown row kind: <row.getRowKind()>
- Could not set a field in the RowDataWrapper because rowData
- Cannot set row kind in the RowDataProjection
- Unknown type for int field. Type name: ${className}
- Unknown type for long field. Type name: ${className}
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/387dbc3bf90ed69d.
Report an issue: GitHub.