apache/druid · error · ParseException

Unable to parse row [%s]

Error message

Unable to parse row [%s]

What it means

JSONToLowerParser.parseToMap parses a JSON row and lowercases all keys (and optionally values) so ingestion is case-insensitive. Any failure while parsing the JSON or building the lowered map is wrapped in a ParseException with the raw input text as the message detail.

Source

Thrown at processing/src/main/java/org/apache/druid/java/util/common/parsers/JSONToLowerParser.java:147

          final List<Object> nodeValue = Lists.newArrayListWithExpectedSize(node.size());
          for (final JsonNode subnode : node) {
            final Object subnodeValue = VALUE_FUNCTION.apply(subnode);
            if (subnodeValue != null) {
              nodeValue.add(subnodeValue);
            }
          }
          map.put(StringUtils.toLowerCase(key), nodeValue); // difference from JSONParser parse()
        } else {
          final Object nodeValue = VALUE_FUNCTION.apply(node);
          if (nodeValue != null) {
            map.put(StringUtils.toLowerCase(key), nodeValue); // difference from JSONParser parse()
          }
        }
      }
      return map;
    }
    catch (Exception e) {
      throw new ParseException(input, e, "Unable to parse row [%s]", input);
    }
  }
}

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Ensure each input record is a complete, valid JSON object before parsing.
  2. Fix upstream producers so records are not truncated or concatenated.
  3. Use the appropriate parser type for the actual payload format.
  4. Skip or quarantine rows that are empty/null before calling the parser.
  5. Catch ParseException per-row in ingestion code and log/forward the bad record instead of aborting.

Example fix

// before
Map<String, Object> row = parser.parseToMap(record.value()); // may throw ParseException
// after
String input = record.value();
try {
  Map<String, Object> row = parser.parseToMap(input);
} catch (ParseException e) {
  log.warn(e, "Bad row, skipping: %s", input);
}
Defensive patterns

Strategy: try-catch

Validate before calling

private static boolean isParsableJsonObject(final ObjectMapper mapper, final String input) {
  if (input == null || input.trim().isEmpty()) { return false; }
  try {
    return mapper.readTree(input).isObject();
  } catch (IOException e) { return false; }
}

Try / catch

try {
  Map<String, Object> row = parser.parseToMap(input);
} catch (ParseException e) {
  log.warn(e, "Bad row skipped: %s", input);
}

Prevention

When it happens

Trigger: Calling parseToMap(String) with input that is not a valid JSON object (malformed JSON, empty/null string, non-object top-level value whose handling breaks key lowering), or an element whose processing (e.g. flattening nested maps) throws.

Common situations: Feeding newline-delimited JSON where a record was split mid-object; passing plain-text or CSV rows to a JSON parser; null/empty messages from a message bus; numeric or array top-level JSON values instead of objects.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/c506d82a54a0edd4. Report an issue: GitHub.