apache/seatunnel · error · UnsupportedOperationException

Unknown row kind: ${rowKind}

Error message

Unknown row kind: ${rowKind}

What it means

changeFlag() maps a SeaTunnel RowKind to a boolean (insert/update-after -> true, delete/update-before -> false); any other RowKind (e.g. EMPTY) hits default and throws UnsupportedOperationException because the sink cannot interpret it as a change flag.

Source

Thrown at seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/writer/HudiRecordWriter.java:212

    protected void prepareRecords(SeaTunnelRow element) {
        HoodieRecord<HoodieAvroPayload> hoodieAvroPayloadHoodieRecord =
                recordConverter.convertRow(schema, seaTunnelRowType, element, hudiTableConfig);
        HoodieKey recordKey = hoodieAvroPayloadHoodieRecord.getKey();
        boolean changeFlag = changeFlag(element.getRowKind());
        buffer.put(recordKey, Pair.of(changeFlag, hoodieAvroPayloadHoodieRecord));
    }

    private boolean changeFlag(RowKind rowKind) {
        switch (rowKind) {
            case DELETE:
            case UPDATE_BEFORE:
                return false;
            case INSERT:
            case UPDATE_AFTER:
                return true;
            default:
                throw new UnsupportedOperationException("Unknown row kind: " + rowKind);
        }
    }

    protected void checkFlushException() {
        if (flushException != null) {
            throw new HudiConnectorException(
                    HudiErrorCode.FLUSH_DATA_FAILED,
                    "Flush records to Hudi failed.",
                    flushException);
        }
    }

    /** Executes prepared statement and closes all resources of this instance. */
    public synchronized void close() {
        if (!closed) {
            closed = true;
            try {
                flush();

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Ensure the source/transform sets an explicit RowKind (INSERT for append-only data)
  2. If appending plain data, use a source or transform that sets RowKind.INSERT
  3. Verify translation/adapter settings so CDC events get proper row kinds

Example fix

// before: row created with RowKind.ENUM_EMPTY
// after
row.setRowKind(RowKind.INSERT);
Defensive patterns

Strategy: type-guard

Validate before calling

RowKind kind = row.getRowKind();
if (!(kind == RowKind.INSERT || kind == RowKind.UPDATE_AFTER
    || kind == RowKind.UPDATE_BEFORE || kind == RowKind.DELETE)) {
    throw new IllegalStateException("RowKind " + kind + " not usable by Hudi CDC sink");
}

Type guard

static boolean hasUsableRowKind(SeaTunnelRow row) {
    RowKind k = row.getRowKind();
    return k == RowKind.INSERT || k == RowKind.UPDATE_AFTER
        || k == RowKind.UPDATE_BEFORE || k == RowKind.DELETE;
}

Try / catch

try {
    sink.writeRecord(row);
} catch (UnsupportedOperationException e) {
    if (e.getMessage().startsWith("Unknown row kind:")) {
        LOG.error("Set an explicit RowKind on rows before the Hudi sink");
    } else { throw e; }
}

Prevention

When it happens

Trigger: writeRecord calls changeFlag(rowKind) on a row whose RowKind is not INSERT, UPDATE_AFTER, UPDATE_BEFORE, or DELETE — typically RowKind.ENUM_EMPTY or a custom kind.

Common situations: Non-CDC source feeding a CDC-style Hudi sink, producing EMPTY row kind; custom transforms not setting RowKind; translation layers emitting unusual kinds.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/8c77c713c5958468. Report an issue: GitHub.