apache/seatunnel · error · IllegalStateException
Cannot deserialize TiDB DELETE CDC row because both value…
Error message
Cannot deserialize TiDB DELETE CDC row because both value and oldValue are empty, and table ${tableInfo.getName()}(${tableInfo.getId()}) is not a pk-handle table. key=${row.getKey()}, startTs=${row.getStartTs()}, commitTs=${row.getCommitTs()} What it means
Thrown by decodeDeleteValuesFromHandle when a DELETE CDC event has neither value nor oldValue bytes, and the table is not a pk-handle table, so the deleted row's values cannot be reconstructed from the row handle. Without value bytes or a handle-capable primary key, the connector cannot produce a meaningful delete record.
Solutions
- Enable old-value capture in TiCDC (changefeed config enable-old-value=true) so DELETE events carry the row bytes
- If old values cannot be enabled, use a table with an integer handle primary key (INT/BIGINT single-column PK)
- Check TiKV client region/key decode: verify key and startTs/commitTs in the message to trace the event
- Upgrade TiDB/TiCDC — newer versions populate old values for delete events
Example fix
// before: TiCDC changefeed without old values
cdc cli changefeed create --sink-uri=... --config=...
// after
// tiCDC changefeed config: {"enable-old-value": true}
cdc cli changefeed update --changefeed-id=... --config=cf.toml Defensive patterns
Strategy: validation
Validate before calling
// Verify TiCDC changefeed has old-value enabled before running CDC jobs cdc cli changefeed query --changefeed-id=<id> | jq '.config.enable_old_value' // must be true for tables without integer handle PKs
Type guard
boolean canDecodeDelete(Cdcpb.Event.Row row, TiTableInfo tableInfo) {
boolean hasValue = row.getValue() != null && !row.getValue().isEmpty();
boolean hasOld = row.getOldValue() != null && !row.getOldValue().isEmpty();
return hasValue || hasOld || (tableInfo.isPkHandle() && tableInfo.getPKIsHandleColumn() != null);
} Try / catch
try {
deserializer.deserialize(row, output);
} catch (IllegalStateException e) {
if (e.getMessage().contains("not a pk-handle table")) {
LOG.error("Enable TiCDC enable-old-value for table {}", tableInfo.getName());
}
throw e;
} Prevention
- Always enable enable-old-value=true in TiCDC changefeeds used by SeaTunnel
- Prefer tables with single-column integer primary keys for CDC
- Test delete-event handling on staging before production
When it happens
Trigger: A DELETE event arrives with empty value and empty oldValue while tableInfo.isPkHandle() is false (or getPKIsHandleColumn() is null) — i.e. the table's primary key is not an integer handle that can be decoded into column values.
Common situations: Tables with non-integer or composite primary keys receiving delete events where TiCDC/TiKV omits the old value (old-value capture disabled in TiCDC configuration); deletes on tables without handle-type PKs.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Commit row has no matched prewrite row yet, hold it and…
- Fail to deserialize row
- Unable to convert TIMESTAMP_TZ from String value
- Unable to convert to decimal from unexpected value
- Unable to convert to LocalDateTime from unexpected value
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/5c3545867645101f.
Report an issue: GitHub.
Appendix: source
Thrown at seatunnel-connectors-v2/connector-cdc/connector-cdc-tidb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/tidb/source/deserializer/SeaTunnelRowStreamingRecordDeserializer.java:104
}
private Object[] decodeDeleteValues(Cdcpb.Event.Row row, long handle) {
ByteString oldValue = row.getOldValue();
if (oldValue != null && !oldValue.isEmpty()) {
return decodeObjects(oldValue.toByteArray(), handle, tableInfo);
}
ByteString value = row.getValue();
if (value != null && !value.isEmpty()) {
// Prefer an available row image before falling back to a PK-only delete row.
return decodeObjects(value.toByteArray(), handle, tableInfo);
}
return decodeDeleteValuesFromHandle(row, handle);
}
private Object[] decodeDeleteValuesFromHandle(Cdcpb.Event.Row row, long handle) {
TiColumnInfo handleColumn = tableInfo.getPKIsHandleColumn();
if (!tableInfo.isPkHandle() || handleColumn == null) {
throw new IllegalStateException(
String.format(
"Cannot deserialize TiDB DELETE CDC row because both value and"
+ " oldValue are empty, and table %s(%s) is not a pk-handle"
+ " table. key=%s, startTs=%s, commitTs=%s",
tableInfo.getName(),
tableInfo.getId(),
row.getKey(),
row.getStartTs(),
row.getCommitTs()));
}
Object[] values = new Object[tableInfo.getColumns().size()];
values[handleColumn.getOffset()] = handle;
return values;
}
}
View on GitHub (pinned to cf67b549a7)