google/gson · error · MalformedJsonException

${message}${locationString()} See ${TroubleshootingGuide.cre

Error message

${message}${locationString()}
See ${TroubleshootingGuide.createUrl("malformed-json")}

What it means

This is the single construction site in JsonReader.syntaxError(String): it builds a MalformedJsonException (an IOException subclass) whose message is '<reason> at line L column C path $...' plus a link to the malformed-json troubleshooting page. Nearly every JSON grammar violation in JsonReader is routed through this method, so the visible message is the reason text (e.g. 'Unterminated string', 'Expected name', 'JSON forbids NaN and infinities', 'Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON'). The location string pinpoints where in the input the parser failed.

Source

Thrown at gson/src/main/java/com/google/gson/stream/JsonReader.java:1840

        if (strictness == Strictness.STRICT) {
          throw syntaxError("Invalid escaped character \"'\" in strict mode");
        }
      case '"':
      case '\\':
      case '/':
        return escaped;
      default:
        // throw error when none of the above cases are matched
        throw syntaxError("Invalid escape sequence");
    }
  }

  /**
   * Throws a new {@link MalformedJsonException} with the given message and information about the
   * current location.
   */
  private MalformedJsonException syntaxError(String message) throws MalformedJsonException {
    throw new MalformedJsonException(
        message + locationString() + "\nSee " + TroubleshootingGuide.createUrl("malformed-json"));
  }

  private IllegalStateException unexpectedTokenError(String expected) throws IOException {
    JsonToken peeked = peek();
    String troubleshootingId =
        peeked == JsonToken.NULL ? "adapter-not-null-safe" : "unexpected-json-structure";
    return new IllegalStateException(
        "Expected "
            + expected
            + " but was "
            + peek()
            + locationString()
            + "\nSee "
            + TroubleshootingGuide.createUrl(troubleshootingId));
  }

  /** Consumes the non-execute prefix if it exists. */

View on GitHub (pinned to 8b8628c656)

Solutions

  1. Inspect the message reason text and location (line/column/path) to find the exact bad token, then fix the producer.
  2. If the input intentionally uses lenient syntax (comments, unquoted keys, NaN), call reader.setStrictness(Strictness.LENIENT) before reading.
  3. Validate/prettify the payload with a strict parser upstream, or run it through JsonReader in LENIENT then re-emit canonical JSON.
  4. For truncated streaming input, ensure the source writes complete JSON before the reader consumes it; for NDJSON, create a new JsonReader per line/document.

Example fix

// before
try (JsonReader reader = new JsonReader(new StringReader(payload))) {
  reader.beginObject(); // throws MalformedJsonException on bad token
}

// after: tolerate lenient producer + handle parse failure
try (JsonReader reader = new JsonReader(new StringReader(payload))) {
  reader.setStrictness(Strictness.LENIENT);
  reader.beginObject();
} catch (MalformedJsonException e) {
  log.warn("Unparseable JSON payload: {}", e.getMessage());
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-validate JSON with a strict parser before handing off, or set LENIENT for tolerant reads
public JsonReader lenientReader(String payload) {
  JsonReader r = new JsonReader(new StringReader(payload));
  r.setStrictness(Strictness.LENIENT); // accept comments, unquoted keys, NaN, etc.
  return r;
}

Type guard

// Cheap structural pre-check before full parse: balanced brackets and non-empty
public boolean looksLikeCompleteJson(String s) {
  if (s == null || s.isBlank()) return false;
  int depth = 0; boolean inStr = false; boolean esc = false;
  for (int i = 0; i < s.length(); i++) {
    char c = s.charAt(i);
    if (esc) { esc = false; continue; }
    if (c == '\\') { esc = true; continue; }
    if (c == '"') { inStr = !inStr; continue; }
    if (inStr) continue;
    if (c == '{' || c == '[') depth++;
    else if (c == '}' || c == ']') depth--;
  }
  return !inStr && depth == 0;
}

Try / catch

try (JsonReader reader = new JsonReader(source)) {
  // ... parse ...
} catch (MalformedJsonException e) {
  // message includes reason + 'at line L column C path $...' + troubleshooting URL
  throw new MyParseException("Bad JSON at " + extractLocation(e.getMessage()), e);
}

Prevention

When it happens

Trigger: Any malformed input to JsonReader: unterminated array/object/string (lines 598, 614, 1192, 1281, 1595), expected name/colon/value (631, 640, 657, 694, 724), NaN/Infinity literal in non-LENIENT mode (1063), invalid escape sequence or malformed \u (1771, 1778, 1792, 1815, 1823, 1831), strict-mode unquoted strings/comments (checkLenient, 1637), non-ASCII in strict strings (1884). Also fires when nextDouble() encounters NaN/Infinity and strictness != LENIENT.

Common situations: Reading truncated network responses; parsing JSON produced by lenient writers that emitted NaN/Infinity, comments, single-quoted strings, or unquoted keys; feeding concatenated NDJSON to one JsonReader; corrupted files with stray BOM/control characters; version skew where a producer uses LENIENT features the consumer (default LEGACY_STRICT) rejects; BOM or trailing data after the root value.

Related errors


AI-assisted analysis of google/gson@8b8628c656 (2026-08-04). Data as JSON: /data/errors/750de0e83fd58ce6.json. Report an issue: GitHub.