apache/seatunnel · error · IllegalStateException

Null data value '${jsonNode}' Cannot send downstream

Error message

Null data value '${jsonNode}' Cannot send downstream

What it means

When a Canal JSON event carries a null or JSON-null 'data' node, the deserializer normally skips query/create/alter DDL events, but for any other op type a null data value cannot produce a row, so it throws IllegalStateException to make the malformed/unsupported event visible rather than emitting an empty row downstream.

Source

Thrown at seatunnel-formats/seatunnel-format-json/src/main/java/org/apache/seatunnel/format/json/canal/CanalJsonDeserializationSchema.java:150

            if (database != null
                    && !databasePattern.matcher(jsonNode.get(FIELD_DATABASE).asText()).matches()) {
                return;
            }
            if (table != null
                    && !tablePattern.matcher(jsonNode.get(FIELD_TABLE).asText()).matches()) {
                return;
            }

            JsonNode dataNode = jsonNode.get(FIELD_DATA);
            String op = jsonNode.get(FIELD_TYPE).asText();
            JsonNode tsNode = jsonNode.get(FIELD_TS);
            // When a null value is encountered, an exception needs to be thrown for easy sensing
            if (dataNode == null || dataNode.isNull()) {
                // We'll skip the query or create or alter event data
                if (OP_QUERY.equals(op) || OP_CREATE.equals(op) || OP_ALTER.equals(op)) {
                    return;
                }
                throw new IllegalStateException(
                        format("Null data value '%s' Cannot send downstream", jsonNode));
            }

            switch (op) {
                case OP_INSERT:
                    for (int i = 0; i < dataNode.size(); i++) {
                        SeaTunnelRow row = convertJsonNode(dataNode.get(i));
                        if (tablePath != null && !tablePath.toString().isEmpty()) {
                            row.setTableId(tablePath.toString());
                        }
                        if (tsNode != null) {
                            MetadataUtil.setEventTime(row, tsNode.asLong());
                        }
                        out.collect(row);
                    }
                    break;
                case OP_UPDATE:
                    final ArrayNode oldNode = (ArrayNode) jsonNode.get(FIELD_OLD);

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Filter out non-data events (heartbeat/ROTATE/control events) before deserialization, e.g. only pass op INSERT/UPDATE/DELETE messages
  2. Skip events where data is null in custom logic by pre-checking the JSON, or patch the deserializer to ignore them like QUERY/CREATE/ALTER
  3. Check Canal server configuration to stop emitting heartbeat/control messages to the consumed channel
  4. Set table/database pattern filters (tablePattern/databasePattern) so unrelated events don't reach this code path

Example fix

// before
{"data":null,"op":"UNKNOWN","es":1699999999,...}
// after (pre-filter)
if (node.path("op").asText("").matches("INSERT|UPDATE|DELETE")
    && !node.path("data").isNull()) {
  canalJsonSchema.deserialize(bytes, out);
}
Defensive patterns

Strategy: validation

Validate before calling

JsonNode op = node.path("op");
JsonNode data = node.path("data");
boolean isDdl = op.asText("").matches("QUERY|CREATE|ALTER");
if (!isDdl && (data.isMissingNode() || data.isNull())) {
    return; // skip non-data event before deserializing
}

Type guard

boolean hasData = node.has("data") && !node.get("data").isNull() && node.get("data").isArray();

Try / catch

try { canalJsonSchema.deserialize(message, out); } catch (IllegalStateException e) { if (e.getMessage().startsWith("Null data value")) { /* skip heartbeat/control event */ } else { throw e; } }

Prevention

When it happens

Trigger: Consuming a Canal (MySQL binlog) JSON message whose op is not QUERY/CREATE/ALTER but whose data field is null or null-literal (e.g. heartbeats, some DELETE/other events with no data payload).

Common situations: Canal server heartbeat or control messages mixed into the monitored queue/topic; binlog events such as ROTATE or unsupported event types forwarded by Canal; filtering not applied upstream on message types.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/84398a80c03818a3. Report an issue: GitHub.