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
- Check entry.getEntryType() and entry.getHeader().getEventType() before calling convert — skip entries you do not expect to contain a RowChange.
- Ensure canal connector and canal server versions are aligned so the RowChange protobuf schema matches.
- 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.
- 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
- Keep canal client and server protobuf versions identical.
- Filter to ROWDATA entries before parsing RowChange.
- Isolate per-entry parsing so one bad entry does not abort the whole convert().
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
- unexpected packet type: ${p.getType()}
- unsupported version at this client.
- compression is not supported in this connector
- deserializer failed by ${e.getMessage()}
- Expected length of at least {} bytes, but had {}
AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14).
Data as JSON: /api/errors/5b5c0ce432610de4.
Report an issue: GitHub.