{"record":{"id":"b9a234412b187b18","repo":"alibaba/canal","slug":"error-the-bfcolumns-is-null","errorCode":null,"errorMessage":"ERROR ## the bfColumns is null","messagePattern":"ERROR ## the bfColumns is null","errorType":"exception","errorClass":"CanalParseException","httpStatus":null,"severity":"error","filePath":"parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java","lineNumber":964,"sourceCode":"        }\n\n        // add current gtid last committed\n        if (StringUtils.isNotEmpty(logHeader.getCurrentGtidLastCommit())) {\n            Pair pair = createSpecialPair(\"curtGtidLct\", logHeader.getCurrentGtidLastCommit());\n            headerBuilder.addProps(pair);\n        }\n\n        // add rowsCount suppport\n        if (rowsCount > 0) {\n            Pair pair = createSpecialPair(\"rowsCount\", String.valueOf(rowsCount));\n            headerBuilder.addProps(pair);\n        }\n        return headerBuilder.build();\n    }\n\n    private boolean isUpdate(List<Column> bfColumns, String newValue, int index) {\n        if (bfColumns == null) {\n            throw new CanalParseException(\"ERROR ## the bfColumns is null\");\n        }\n\n        if (index < 0) {\n            return false;\n        }\n\n        for (Column column : bfColumns) {\n            if (column.getIndex() == index) {// 比较before / after的column index\n                if (column.getIsNull() && newValue == null) {\n                    // 如果全是null\n                    return false;\n                } else if (newValue != null && (!column.getIsNull() && column.getValue().equals(newValue))) {\n                    // fixed issue #135, old column is Null\n                    // 如果不为null，并且相等\n                    return false;\n                }\n            }\n        }","sourceCodeStart":946,"sourceCodeEnd":982,"githubUrl":"https://github.com/alibaba/canal/blob/87be50e87686a3e8af08c368d0e1ffd1f59eb04a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java#L946-L982","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set binlog_row_image=FULL on the MySQL server: SET GLOBAL binlog_row_image='FULL'; to ensure complete before/after images.","Upgrade Canal — newer versions handle MINIMAL row image without calling isUpdate on null bfColumns.","If you see this consistently, report it as a Canal bug with the binlog_row_image setting and event details."],"exampleFix":"-- before\nmysql> SET GLOBAL binlog_row_image = 'MINIMAL';\n\n-- after\nmysql> SET GLOBAL binlog_row_image = 'FULL';\n-- add to my.cnf for persistence:\n-- [mysqld]\n-- binlog_row_image=FULL","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// isUpdate is internal; the outer parseRowsEvent catch handles it:\ntry {\n    Entry entry = parseRowsEvent(event);\n} catch (CanalParseException e) {\n    if (e.getMessage().contains(\"bfColumns is null\")) {\n        // Likely binlog_row_image=MINIMAL — log and report\n        logger.error(\"bfColumns null — check binlog_row_image setting on MySQL\", e);\n        // This is likely a Canal bug; report upstream\n    }\n}","preventionTips":["Set binlog_row_image=FULL on the MySQL server to ensure complete before-images for UPDATE events.","Keep Canal updated — newer versions handle MINIMAL row image mode correctly.","Report consistent occurrences of this error as a Canal bug with server version and binlog_row_image setting."],"tags":["mysql","binlog","update","row-image","internal-error"],"backgroundTag":null,"analyzedSha":"87be50e87686a3e8af08c368d0e1ffd1f59eb04a","analyzedAt":"2026-08-14T04:30:11.918Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}