apache/flink · error · java.lang.IllegalStateException

Illegal JSON object data...

Error message

Illegal JSON object data...

What it means

IllegalStateException from createRowConverter's lambda when the root token is not START_OBJECT — the JSON record for a ROW-typed schema must be a JSON object '{...}', but the payload at that position is a scalar, array, or the stream is malformed at that point. This guards every top-level row and nested ROW conversion.

Source

Thrown at flink-formats/flink-json/src/main/java/org/apache/flink/formats/json/JsonParserToRowDataConverters.java:407

        };
    }

    public JsonParserToRowDataConverter createRowConverter(RowType rowType) {
        final JsonParserToRowDataConverter[] fieldConverters =
                rowType.getFields().stream()
                        .map(RowType.RowField::getType)
                        .map(this::createConverter)
                        .toArray(JsonParserToRowDataConverter[]::new);
        final String[] fieldNames = rowType.getFieldNames().toArray(new String[0]);

        Map<String, Integer> nameIdxMap = new HashMap<>();
        for (int i = 0; i < rowType.getFieldCount(); i++) {
            nameIdxMap.put(fieldNames[i], i);
        }

        return jp -> {
            if (jp.currentToken() != JsonToken.START_OBJECT) {
                throw new IllegalStateException("Illegal JSON object data...");
            }
            int arity = nameIdxMap.size();
            GenericRowData row = new GenericRowData(arity);
            int cnt = 0;
            jp.nextToken();
            while (jp.currentToken() != JsonToken.END_OBJECT) {
                if (cnt >= arity) {
                    skipToNextField(jp);
                    continue;
                }
                String fieldName = jp.getText();
                jp.nextToken();
                Integer idx = nameIdxMap.get(fieldName);
                if (idx != null) {
                    try {
                        Object convertField = fieldConverters[idx].convert(jp);
                        row.setField(idx, convertField);
                    } catch (Throwable t) {

View on GitHub (pinned to 2f3c205e92)

Solutions

  1. Ensure each record is a complete JSON object matching the DDL columns
  2. Filter/quarantine non-object messages at the source (e.g., a preceding filter or topic separation)
  3. With 'json.ignore-parse-errors'='true' such records are skipped instead of failing the job

Example fix

// producer: before
producer.send(new ProducerRecord<>(topic, "42"));

// after
producer.send(new ProducerRecord<>(topic, "{\"value\": 42}"));
Defensive patterns

Strategy: validation

Validate before calling

JsonNode n = mapper.readTree(sample); // root must be an object for row schemas
if (!n.isObject()) throw new IllegalStateException("each record must be a JSON object");

Try / catch

catch (IllegalStateException e) {
    if (e.getMessage().contains("Illegal JSON object data")) {
        // non-object record hit a ROW schema; filter or fix producer
    }
}

Prevention

When it happens

Trigger: Kafka message contains a bare number/string ('42', '"ok"') instead of an object; a JSON array element is not an object when converting nested ROWs; trailing garbage making the token stream diverge.

Common situations: Topics mixing control/heartbeat messages with data; producers sending JSON-encoded scalars for empty payloads; newline-delimited JSON split incorrectly.

Related errors


AI-assisted analysis of apache/flink@2f3c205e92 (2026-08-14). Data as JSON: /api/errors/8ed0d44259e13e8d. Report an issue: GitHub.