alibaba/canal · error · RuntimeException

ERROR ## parser of eromanga-event has an error , data:${entr

Error message

ERROR ## parser of eromanga-event has an error , data:${entry.toString()}

What it means

Thrown by MessageUtil.convert when CanalEntry.RowChange.parseFrom(entry.getStoreValue()) fails for a non-transaction entry. 'eromanga-event' is canal's internal codename for a row-data (ROWDATA) entry; the storeValue on such an entry must be a serialized RowChange. A parse failure means the entry's payload is missing, corrupted, or holds a structure that is not a RowChange.

Source

Thrown at connector/core/src/main/java/com/alibaba/otter/canal/connector/core/util/MessageUtil.java:38

public class MessageUtil {

    public static List<CommonMessage> convert(Message message) {
        if (message == null) {
            return null;
        }
        List<CanalEntry.Entry> entries = message.getEntries();
        List<CommonMessage> msgs = new ArrayList<>(entries.size());
        for (CanalEntry.Entry entry : entries) {
            if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
                || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
                continue;
            }

            CanalEntry.RowChange rowChange;
            try {
                rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
                    e);
            }

            CanalEntry.EventType eventType = rowChange.getEventType();

            final CommonMessage msg = new CommonMessage();
            msg.setIsDdl(rowChange.getIsDdl());
            msg.setDatabase(entry.getHeader().getSchemaName());
            msg.setTable(entry.getHeader().getTableName());
            msg.setType(eventType.toString());
            msg.setEs(entry.getHeader().getExecuteTime());
            msg.setIsDdl(rowChange.getIsDdl());
            msg.setTs(System.currentTimeMillis());
            msg.setSql(rowChange.getSql());
            msgs.add(msg);
            List<Map<String, Object>> data = new ArrayList<>();
            List<Map<String, Object>> old = new ArrayList<>();

View on GitHub (pinned to 87be50e876)

Solutions

  1. Check entry.getEntryType() and entry.getHeader().getEventType() before calling convert — skip entries you do not expect to contain a RowChange.
  2. Ensure canal connector and canal server versions are aligned so the RowChange protobuf schema matches.
  3. If converting arbitrary Messages, wrap convert() per-message so one bad entry does not abort the whole batch, and inspect the offending entry.toString() shown in the message.
  4. For heartbeat/DDL entries, handle them explicitly instead of letting RowChange.parseFrom run on a non-row payload.

Example fix

// before
List<CommonMessage> msgs = MessageUtil.convert(message);

// after — filter to ROWDATA entries and isolate parse failures
for (CanalEntry.Entry entry : message.getEntries()) {
    if (entry.getEntryType() != CanalEntry.EntryType.ROWDATA) continue;
    try {
        CanalEntry.RowChange rc = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
        // ... build CommonMessage
    } catch (Exception e) {
        log.warn("skip unparseable entry {}", entry.getHeader(), e);
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Skip entries that cannot contain a RowChange before convert()
List<CanalEntry.Entry> rowdata = message.getEntries().stream()
    .filter(e -> e.getEntryType() == CanalEntry.EntryType.ROWDATA)
    .collect(Collectors.toList());

Type guard

static boolean isRowDataEntry(CanalEntry.Entry e) {
    return e.getEntryType() == CanalEntry.EntryType.ROWDATA
        && e.getStoreValue() != null
        && !e.getStoreValue().isEmpty();
}

Try / catch

for (CanalEntry.Entry entry : message.getEntries()) {
    if (entry.getEntryType() != CanalEntry.EntryType.ROWDATA) continue;
    try {
        CanalEntry.RowChange rc = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
        // build CommonMessage...
    } catch (Exception e) {
        log.warn("skipping unparseable entry {}", entry.getHeader(), e);
    }
}

Prevention

When it happens

Trigger: MessageUtil.convert(message) iterates entries; for any entry whose EntryType is not TRANSACTIONBEGIN/TRANSACTIONEND it calls RowChange.parseFrom(entry.getStoreValue()). Fails when storeValue is empty, contains a heartbeat/DDL marker that is not a RowChange, or is a newer-protobuf RowChange the client cannot parse.

Common situations: Mixing canal client/server versions where the RowChange proto schema differs; a heartbeat entry mistakenly typed as ROWDATA; memory/disk corruption of the storeValue; an entry produced by a custom plugin that wrote a non-RowChange payload into storeValue.

Related errors


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