apache/seatunnel · error · IllegalArgumentException

Unknown Row Op Type

Error message

Unknown Row Op Type: ${row.getOpType()}

What it means

Thrown by SeaTunnelRowStreamingRecordDeserializer.deserialize when a TiKV CDC event row carries an op type (RowOpType) that the switch statement does not handle — only PUT, DELETE and UPDATE-after-PUT variants are expected. It signals an unknown/unsupported operation type in the CDC protocol event.

Solutions

  1. Upgrade connector-cdc-tidb (and its bundled TiKV client proto) to match your TiDB cluster version
  2. Log and inspect row.getOpType() to identify the unhandled value
  3. Downgrade/check TiCDC compatibility if you cannot upgrade the connector
  4. File/verify support for the missing op type in the connector's deserialize switch

Example fix

// before
throw new IllegalArgumentException("Unknown Row Op Type: " + row.getOpType());
// after: upgrade connector version so all TiKV RowOpType values are handled in the switch
Defensive patterns

Strategy: type-guard

Validate before calling

// Check op type before deserializing
Object opType = row.getOpType();
if (opType != RowOpType.PUT && opType != RowOpType.DELETE) {
    LOG.warn("Skipping unhandled RowOpType {} at ts {}", opType, row.getCommitTs());
}

Type guard

static boolean isSupportedOp(Cdcpb.Event.Row row) {
    return row.getOpType() == RowOpType.PUT
        || row.getOpType() == RowOpType.DELETE;
}

Try / catch

try {
    deserializer.deserialize(row, output);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Unknown Row Op Type")) {
        LOG.warn("Skipping unknown op type: {}", row.getOpType());
    } else { throw e; }
}

Prevention

When it happens

Trigger: The Cdcpb.Event.Row.getOpType() returns a value outside the handled enum cases, typically due to a TiKV/TiCDC protocol version mismatch or a newly introduced op type the connector does not yet support.

Common situations: Deploying a connector built against an older TiKV client proto against a newer TiDB/TiKV cluster that emits new op types; corrupted or partially parsed CDC event stream.

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/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/4b6f9d7a23c2fde0. Report an issue: GitHub.

Appendix: source

Thrown at seatunnel-connectors-v2/connector-cdc/connector-cdc-tidb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/tidb/source/deserializer/SeaTunnelRowStreamingRecordDeserializer.java:84

                    if (row.getOldValue() == null || row.getOldValue().isEmpty()) {
                        SeaTunnelRow insert = converter.convert(values, tableInfo, rowType);
                        insert.setRowKind(RowKind.INSERT);
                        collect(insert, output);
                    } else {
                        SeaTunnelRow update = converter.convert(values, tableInfo, rowType);
                        update.setRowKind(RowKind.UPDATE_AFTER);
                        collect(update, output);
                    }
                    break;
                } catch (final RuntimeException e) {
                    throw new RuntimeException(
                            String.format(
                                    "Fail to deserialize row: %s, table: %s",
                                    row, tableInfo.getId()),
                            e);
                }
            default:
                throw new IllegalArgumentException("Unknown Row Op Type: " + row.getOpType());
        }
    }

    private Object[] decodeDeleteValues(Cdcpb.Event.Row row, long handle) {
        ByteString oldValue = row.getOldValue();
        if (oldValue != null && !oldValue.isEmpty()) {
            return decodeObjects(oldValue.toByteArray(), handle, tableInfo);
        }
        ByteString value = row.getValue();
        if (value != null && !value.isEmpty()) {
            // Prefer an available row image before falling back to a PK-only delete row.
            return decodeObjects(value.toByteArray(), handle, tableInfo);
        }
        return decodeDeleteValuesFromHandle(row, handle);
    }

    private Object[] decodeDeleteValuesFromHandle(Cdcpb.Event.Row row, long handle) {
        TiColumnInfo handleColumn = tableInfo.getPKIsHandleColumn();

View on GitHub (pinned to cf67b549a7)