apache/iceberg · error · UnsupportedOperationException
Unknown row kind:
Error message
Unknown row kind:
What it means
BaseDeltaTaskWriter.write() dispatches RowData by RowKind: INSERT/UPDATE_AFTER go to the data writer, DELETE/UPDATE_BEFORE go to the delete writer. Any other RowKind cannot be written to Iceberg and triggers UnsupportedOperationException naming the kind.
Source
Thrown at flink/v2.3/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
- Inspect the record's RowKind in the upstream operator and ensure it's set to one of INSERT, UPDATE_BEFORE, UPDATE_AFTER, DELETE
- Fix the producer emitting records with an invalid RowKind byte (custom serialization/deserialization bugs)
- If intentionally dropping rows, filter them before the sink instead of sending unknown kinds
- Validate changelog mode of the upstream: rows must come from a to-changelog stream with proper kinds
Example fix
// before generic.setRowKind(RowKind.fromByteValue((byte) 9)); // unknown kind -> UnsupportedOperationException // after generic.setRowKind(RowKind.INSERT);
Defensive patterns
Strategy: validation
Validate before calling
// before the sink
DataStream<RowData> validated = stream.filter(r ->
r.getRowKind() == RowKind.INSERT || r.getRowKind() == RowKind.UPDATE_AFTER ||
r.getRowKind() == RowKind.UPDATE_BEFORE || r.getRowKind() == RowKind.DELETE); Type guard
static boolean isWritableRowKind(RowData row) {
switch (row.getRowKind()) {
case INSERT:
case UPDATE_AFTER:
case UPDATE_BEFORE:
case DELETE:
return true;
default:
return false;
}
} Try / catch
try {
writer.write(row);
} catch (UnsupportedOperationException e) {
LOG.error("Row with unsupported RowKind {} reached the Iceberg sink", row.getRowKind(), e);
throw e;
} Prevention
- Ensure upstream operators only emit the four standard RowKinds
- Filter out unknown-kind records before the Iceberg sink
- Check custom RowData implementations for corrupted kind bytes
When it happens
Trigger: Writing a RowData whose getRowKind() is not INSERT, UPDATE_AFTER, DELETE, or UPDATE_BEFORE — only possible with RowKind not produced by normal Flink changelog semantics, or when upstream code constructs RowData with a custom/invalid kind byte.
Common situations: Custom upstream operators building RowData and setting a wrong/unknown RowKind byte; corruption when translating records from another source; hand-crafted test/generic records with kind 0 (unknown).
Related errors
- Unknown row kind:
- Unknown row kind: <row.getRowKind()>
- WriterSink is used only for writing; committing is handled b
- Altering schema is not supported in the old alterTable API.
- Altering partition keys is not supported yet.
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/8e804ffa5214291e.
Report an issue: GitHub.