alibaba/canal · error · IllegalArgumentException

reading operation type (invalid operation code)

Error message

reading operation type (invalid operation code)

What it means

Thrown during the first pass of JSON partial-update diff parsing (MySQL 8.0 JSON partial updates in binlog). The operation code byte read via getUint8() is >= JSON_DIFF_OPERATION_COUNT (3), meaning it does not correspond to any known diff operation: 0=REPLACE, 1=INSERT, 2=REMOVE. This indicates the diff payload is corrupt, misaligned, or produced by a MySQL version with additional operation types.

Source

Thrown at dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/JsonDiffConversion.java:51

     * has the same effect as `JSON_REMOVE(col, path)`.
     */
    public static final int DIFF_OPERATION_REMOVE     = 2;

    public static final int JSON_DIFF_OPERATION_COUNT = 3;

    public static StringBuilder print_json_diff(LogBuffer buffer, long len, String columnName, int columnIndex,
                                                String charsetName) {
        return print_json_diff(buffer, len, columnName, columnIndex, Charset.forName(charsetName));
    }

    public static StringBuilder print_json_diff(LogBuffer buffer, long len, String columnName, int columnIndex,
                                                Charset charset) {
        int position = buffer.position();
        List<String> operation_names = new ArrayList<>();
        while (buffer.hasRemaining()) {
            int operation_int = buffer.getUint8();
            if (operation_int >= JSON_DIFF_OPERATION_COUNT) {
                throw new IllegalArgumentException("reading operation type (invalid operation code)");
            }

            // skip path
            long path_length = buffer.getPackedLong();
            if (path_length > len) {
                throw new IllegalArgumentException("skipping path");
            }

            // compute operation name
            byte[] lastP = buffer.getData(buffer.position() + (int) path_length - 1, 1);
            String operation_name = json_diff_operation_name(operation_int, lastP[0]);
            operation_names.add(operation_name);

            buffer.forward((int) path_length);
            // skip value
            if (operation_int != DIFF_OPERATION_REMOVE) {
                long value_length = buffer.getPackedLong();
                if (value_length > len) {

View on GitHub (pinned to 87be50e876)

Solutions

  1. Verify the column is actually a JSON partial-update diff and not a regular JSON value or other type.
  2. Check MySQL server version for new JSON diff operation types; upgrade canal/dbsync if a newer MySQL added operations beyond REPLACE/INSERT/REMOVE.
  3. Hex-dump the diff payload to confirm the operation byte and detect misalignment.
  4. Upgrade to a canal version that matches the MySQL 8.0 minor version generating the binlog.

Example fix

// before
int operation_int = buffer.getUint8();
if (operation_int >= JSON_DIFF_OPERATION_COUNT) {
    throw new IllegalArgumentException("reading operation type (invalid operation code)");
}

// after: log and skip unknown operations instead of crashing
int operation_int = buffer.getUint8();
if (operation_int >= JSON_DIFF_OPERATION_COUNT) {
    logger.warn("Unknown JSON diff operation code {} at pos {}, skipping diff", operation_int, buffer.position());
    return builder;
}
Defensive patterns

Strategy: validation

Validate before calling

// The check already exists in the library; callers can pre-validate
// by ensuring the column is a JSON partial-update diff before calling print_json_diff
if (columnType != Types.JSON || !isPartialUpdateDiff(buffer)) {
    // not a JSON diff, skip print_json_diff
    return;
}

Try / catch

try {
    StringBuilder sb = JsonDiffConversion.print_json_diff(buffer, len, columnName, columnIndex, charset);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("invalid operation code")) {
        logger.warn("Unknown JSON diff operation in column {} at pos={}, MySQL version may be unsupported",
            columnName, buffer.position());
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling JsonDiffConversion.print_json_diff() on a buffer positioned at a JSON diff column value from a MySQL 8.0 partial JSON update row event. The first byte of each operation entry is the operation code; if it reads >= 3, the exception fires.

Common situations: MySQL 8.0+ introducing new JSON partial-update operation types not yet supported by the parser, buffer position misalignment from a prior column parse error, binlog corruption, or processing a non-diff value as if it were a diff (column type mapping error).

Related errors


AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14). Data as JSON: /api/errors/12825e7ec00edfc0. Report an issue: GitHub.