apache/seatunnel · error · SeaTunnelRuntimeException

COMMON-02

COMMON-02

Error message

Json JSON convert/parse '<payload>' operation failed.

What it means

CanalJsonSerializationSchema.serialize wraps any Throwable from building or JSON-serializing the internal reuse row into jsonOperationError. The offending SeaTunnelRow's toString is embedded. It means the row did not match the schema the Canal serializer expects (wrong arity, incompatible field types) or Jackson failed to serialize it.

Solutions

  1. Compare row.toString() from the error against the declared sink schema and fix the producing transform/source so field types match
  2. Verify that the EVENT_TIME option value is a JSON-serializable type (e.g. timestamp string or long millis)
  3. Regenerate/align the database schema used to build the serializer so it matches incoming rows
  4. Catch in a test (runTest) with the exact row to reproduce, and fix the data or schema before deploying

Example fix

// before: row has 4 fields but Canal schema expects 6 (incl. 'type','ts')
row = new SeaTunnelRow(new Object[]{id, name});
// after: construct row matching createJsonRowType arity
row = new SeaTunnelRow(new Object[]{before, after, "UPDATE", ts, ...});
Defensive patterns

Strategy: validation

Validate before calling

// before serialize, check row conforms to canal schema
if (row.getArity() != expectedType.getTotalFields()) {
    throw new IllegalArgumentException("Row arity mismatch: " + row);
}

Type guard

boolean matchesCanalSchema(SeaTunnelRow row, SeaTunnelRowType t) {
    return row != null && row.getArity() == t.getTotalFields();
}

Try / catch

try {
    byte[] out = serializer.serialize(row);
} catch (SeaTunnelRuntimeException e) {
    log.error("Cannot serialize row {} to canal JSON", row, e);
    throw e; // serialization failure should fail fast
}

Prevention

When it happens

Trigger: serialize(SeaTunnelRow) is called with a row whose field count/type differs from the Canal JSON row type (database schema + 'type','data','old','ts' metadata), or whose values (e.g. byte[]/BigDecimal) cannot be JSON-serialized.

Common situations: Upstream transform changed row arity or types before the sink; event-time option (EVENT_TIME) holds a non-serializable type; null rows or rows of the wrong RowKind passed to a Canal-format sink; schema drift between source and sink.

Understand the failure class

Background: "JSON serialization failed", "not JSON serializable", "Failed to serialize": why JSON marshaling errors happen and how to fix them — this error's family across 46 libraries.

Related errors


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

Appendix: source

Thrown at seatunnel-formats/seatunnel-format-json/src/main/java/org/apache/seatunnel/format/json/canal/CanalJsonSerializationSchema.java:99

            } else {
                reuse.setField(0, null);
            }

            reuse.setField(1, new SeaTunnelRow[] {row});
            reuse.setField(2, rowKind2String(row.getRowKind()));

            if (!StringUtils.isEmpty(row.getTableId())) {
                reuse.setField(3, TablePath.of(row.getTableId()).getDatabaseName());
                reuse.setField(4, TablePath.of(row.getTableId()).getTableName());
            }

            if (row.getOptions() != null && row.getOptions().containsKey(EVENT_TIME.getName())) {
                reuse.setField(5, row.getOptions().get(EVENT_TIME.getName()));
            }

            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));

View on GitHub (pinned to cf67b549a7)