apache/seatunnel · error · SeaTunnelJsonFormatException
UNSUPPORTED_OPERATION
UNSUPPORTED_OPERATION
Error message
Unsupported operation ${rowKind} for row kind. What it means
CanalJsonSerializationSchema maps a SeaTunnelRow's RowKind to a Canal JSON 'op' value (INSERT/UPDATE/DELETE). Only INSERT, UPDATE_BEFORE/UPDATE_AFTER and DELETE have Canal equivalents; any other RowKind (typically null or an unexpected kind) has no representation, so the serializer throws SeaTunnelJsonFormatException with UNSUPPORTED_OPERATION. Canal's CDC protocol simply cannot express such operations.
Source
Thrown at seatunnel-formats/seatunnel-format-json/src/main/java/org/apache/seatunnel/format/json/canal/CanalJsonSerializationSchema.java:115
return jsonSerializer.serialize(reuse);
} catch (Throwable t) {
throw CommonError.jsonOperationError(FORMAT, row.toString(), t);
}
}
private String rowKind2String(RowKind rowKind) {
switch (rowKind) {
case INSERT:
case UPDATE_AFTER:
if (mergeUpdateEventFlag && rowKind.equals(RowKind.UPDATE_AFTER)) {
return OP_UPDATE;
}
return OP_INSERT;
case UPDATE_BEFORE:
case DELETE:
return OP_DELETE;
default:
throw new SeaTunnelJsonFormatException(
CommonErrorCodeDeprecated.UNSUPPORTED_OPERATION,
String.format("Unsupported operation %s for row kind.", rowKind));
}
}
private static SeaTunnelRowType createJsonRowType(SeaTunnelRowType databaseSchema) {
return new SeaTunnelRowType(
new String[] {"old", "data", "type", "database", "table", "ts"},
new SeaTunnelDataType[] {
new ArrayType<>(SeaTunnelRowType[].class, databaseSchema),
new ArrayType<>(SeaTunnelRowType[].class, databaseSchema),
STRING_TYPE,
STRING_TYPE,
STRING_TYPE,
LONG_TYPE
});
}
}View on GitHub (pinned to cf67b549a7)
Solutions
- Ensure every row emitted upstream has setRowKind(INSERT|UPDATE_AFTER|UPDATE_BEFORE|DELETE) before reaching the sink
- Fix the source/transform responsible for the unsupported RowKind
- Switch the sink format away from canal-json if non-CDC row kinds are legitimately needed
- Log row.getRowKind() at the sink boundary while debugging the pipeline
Example fix
// before SeaTunnelRow row = new SeaTunnelRow(fields); // RowKind never set -> null // after SeaTunnelRow row = new SeaTunnelRow(fields); row.setRowKind(RowKind.INSERT); // or UPDATE_AFTER / UPDATE_BEFORE / DELETE
Defensive patterns
Strategy: validation
Validate before calling
if (row.getRowKind() == null || !EnumSet.of(RowKind.INSERT, RowKind.UPDATE_AFTER, RowKind.UPDATE_BEFORE, RowKind.DELETE).contains(row.getRowKind())) {
throw new IllegalArgumentException("RowKind not serializable by canal-json: " + row.getRowKind());
} Type guard
boolean isCanalSerializable(SeaTunnelRow row) {
RowKind k = row != null ? row.getRowKind() : null;
return k == RowKind.INSERT || k == RowKind.UPDATE_AFTER || k == RowKind.UPDATE_BEFORE || k == RowKind.DELETE;
} Prevention
- Always call setRowKind on rows produced by custom sources/transforms
- Unit-test serializers with one row per supported RowKind
- Pin RowKind handling when upgrading SeaTunnel versions
When it happens
Trigger: Calling serialize(row) on a SeaTunnelRow whose RowKind is null or not one of INSERT, UPDATE_AFTER, UPDATE_BEFORE, DELETE — e.g. a row constructed without setRowKind, or emitted by a custom transform with an exotic kind.
Common situations: Custom sources/transforms that forget to call row.setRowKind(...); pipelines where an upstream change introduces a new RowKind not anticipated by a sink configured with format=canal-json; version upgrades adding new RowKind values.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- UNSUPPORTED_OPERATION
- ILLEGAL_ARGUMENT
- Unsupported operation '%s' for row kind.
- Unsupported rowKind: " + rowKind
- UNSUPPORTED_ROW_KIND
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/37cf0a06e3fc9493.
Report an issue: GitHub.