apache/seatunnel · error · IllegalStateException

Unknown operation type '${op}'.

Error message

Unknown operation type '${op}'.

What it means

Debezium change events carry an 'op' field ('c' create, 'u' update, 'd' delete, 'r' read/snapshot). parsePayload switches on this value and throws IllegalStateException for anything else, since SeaTunnel cannot map an unknown operation to a RowKind. This guards against malformed, corrupted, or forward-incompatible Debezium messages.

Source

Thrown at seatunnel-formats/seatunnel-format-json/src/main/java/org/apache/seatunnel/format/json/debezium/DebeziumJsonDeserializationSchema.java:179

                out.collect(after);
                break;
            case OP_DELETE:
                SeaTunnelRow delete = debeziumRowConverter.parse(payload.get(DATA_BEFORE));
                if (delete == null) {
                    throw new IllegalStateException(
                            String.format(REPLICA_IDENTITY_EXCEPTION, "DELETE"));
                }
                delete.setRowKind(RowKind.DELETE);
                if (tablePath != null) {
                    delete.setTableId(tablePath.toString());
                }
                if (tsNode != null) {
                    MetadataUtil.setEventTime(delete, tsNode.asLong());
                }
                out.collect(delete);
                break;
            default:
                throw new IllegalStateException(format("Unknown operation type '%s'.", op));
        }
    }

    @Override
    public SeaTunnelDataType<SeaTunnelRow> getProducedType() {
        return this.rowType;
    }

    private JsonNode getPayload(JsonNode jsonNode) {
        if (debeziumEnabledSchema) {
            return jsonNode.get(DATA_PAYLOAD);
        }
        return jsonNode;
    }
}

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Inspect the offending message's 'op' value and confirm it is one of c/u/d/r
  2. Filter unsupported event types (truncate/message) using Debezium SMTs or topic routing
  3. Enable the format's ignoreParseErrors option if skipping unparseable messages is acceptable
  4. Align SeaTunnel and Debezium versions so the set of supported ops matches

Example fix

// before: raw topic includes truncate events (op='t') -> parse fails
// after: drop them upstream with a Debezium SMT / RegexRouter
"transforms": "route",
"transforms.route.type": "org.apache.kafka.connect.transforms.RegexRouter",
"transforms.route.regex": "cdc_(.*)",
"transforms.route.replacement": "$1"
Defensive patterns

Strategy: try-catch

Validate before calling

JsonNode op = payload.get("op");
if (op == null || !"cudr".contains(op.asText(""))) {
    // skip or route to DLQ before parsing
}

Try / catch

try {
    deserializer.deserialize(message, out);
} catch (IllegalStateException e) {
    if (e.getMessage().startsWith("Unknown operation type")) {
        log.warn("Skipping unsupported Debezium op: {}", e.getMessage());
        return; // or send to DLQ
    }
    throw e;
}

Prevention

When it happens

Trigger: A message JSON reaching parsePayload whose op field is missing, null, empty, or an unexpected value (e.g. 't' truncate, 'm' message, or a corrupted character).

Common situations: Non-data change events (truncate, heartbeat/message events) flowing through the CDC topic; Debezium version drift producing new op values; hand-crafted or test payloads missing 'op'; Kafka topic routing delivering non-change-event envelopes.

Related errors


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