alibaba/canal · error · CanalParseException

ERROR ## the bfColumns is null

Error message

ERROR ## the bfColumns is null

What it means

Thrown from isUpdate() when the bfColumns parameter (the 'before' image columns list for an UPDATE event) is null. The method needs to compare before/after column values to determine which columns changed, so a null before-list is an unrecoverable internal state error.

Source

Thrown at parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java:964

        }

        // add current gtid last committed
        if (StringUtils.isNotEmpty(logHeader.getCurrentGtidLastCommit())) {
            Pair pair = createSpecialPair("curtGtidLct", logHeader.getCurrentGtidLastCommit());
            headerBuilder.addProps(pair);
        }

        // add rowsCount suppport
        if (rowsCount > 0) {
            Pair pair = createSpecialPair("rowsCount", String.valueOf(rowsCount));
            headerBuilder.addProps(pair);
        }
        return headerBuilder.build();
    }

    private boolean isUpdate(List<Column> bfColumns, String newValue, int index) {
        if (bfColumns == null) {
            throw new CanalParseException("ERROR ## the bfColumns is null");
        }

        if (index < 0) {
            return false;
        }

        for (Column column : bfColumns) {
            if (column.getIndex() == index) {// 比较before / after的column index
                if (column.getIsNull() && newValue == null) {
                    // 如果全是null
                    return false;
                } else if (newValue != null && (!column.getIsNull() && column.getValue().equals(newValue))) {
                    // fixed issue #135, old column is Null
                    // 如果不为null,并且相等
                    return false;
                }
            }
        }

View on GitHub (pinned to 87be50e876)

Solutions

  1. Set binlog_row_image=FULL on the MySQL server: SET GLOBAL binlog_row_image='FULL'; to ensure complete before/after images.
  2. Upgrade Canal — newer versions handle MINIMAL row image without calling isUpdate on null bfColumns.
  3. If you see this consistently, report it as a Canal bug with the binlog_row_image setting and event details.

Example fix

-- before
mysql> SET GLOBAL binlog_row_image = 'MINIMAL';

-- after
mysql> SET GLOBAL binlog_row_image = 'FULL';
-- add to my.cnf for persistence:
-- [mysqld]
-- binlog_row_image=FULL
Defensive patterns

Strategy: try-catch

Try / catch

// isUpdate is internal; the outer parseRowsEvent catch handles it:
try {
    Entry entry = parseRowsEvent(event);
} catch (CanalParseException e) {
    if (e.getMessage().contains("bfColumns is null")) {
        // Likely binlog_row_image=MINIMAL — log and report
        logger.error("bfColumns null — check binlog_row_image setting on MySQL", e);
        // This is likely a Canal bug; report upstream
    }
}

Prevention

When it happens

Trigger: isUpdate(bfColumns, newValue, index) is called with bfColumns == null. This happens in the UPDATE row-parsing path when the before-image columns were not populated — typically because the binlog event's before-image is empty (binlog_row_image=MINIMAL strips the before-image for unchanged columns) and the code path didn't handle the null case before calling isUpdate.

Common situations: binlog_row_image is set to MINIMAL on the MySQL server, which strips the before-image for UPDATE events, causing bfColumns to be null when Canal expects a full before-image. This is an internal Canal bug or a configuration mismatch between expected and actual binlog_row_image. Rare in practice with modern Canal versions.

Related errors


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