google/gson · error · IllegalStateException

JSON must have only one top-level value.

Error message

JSON must have only one top-level value.

What it means

Thrown by JsonWriter.beforeValue() (IllegalStateException) at JsonWriter.java:810 when a value is about to be written and the top-of-stack scope is already NONEMPTY_DOCUMENT, meaning a complete top-level JSON value was already produced. In Strictness.STRICT and LEGACY_STRICT (the default) RFC 8259 forbids more than one top-level value, so a second value/literal/array/object is rejected. Only Strictness.LENIENT falls through to allow concatenated top-level values.

Solutions

  1. Wrap multiple top-level values in a single array: beginArray() ... values ... endArray().
  2. If you genuinely need concatenated top-level values (NDJSON), call writer.setStrictness(Strictness.LENIENT) before writing.
  3. Use a fresh JsonWriter per top-level value instead of reusing one writer across values.
  4. Restructure the loop so each iteration produces exactly one complete document and flushes/closes before the next.

Example fix

// before
JsonWriter w = new JsonWriter(out); // default LEGACY_STRICT
w.value("first");
w.value("second"); // throws 'JSON must have only one top-level value.'

// after (option A: wrap in array)
JsonWriter w = new JsonWriter(out);
w.beginArray();
w.value("first");
w.value("second");
w.endArray();

// after (option B: lenient for NDJSON)
JsonWriter w = new JsonWriter(out);
w.setStrictness(Strictness.LENIENT);
w.value("first");
w.value("second");
Defensive patterns

Strategy: validation

Validate before calling

// Decide up front: multiple top-level values need an array OR lenient mode.
JsonWriter w = new JsonWriter(out);
boolean multipleTopLevel = values.size() > 1;
if (multipleTopLevel) {
  // choose ONE strategy:
  // w.setStrictness(Strictness.LENIENT);  // for NDJSON
  w.beginArray();              // or wrap
}

Type guard

null

Try / catch

try {
  writer.value(first);
  writer.value(second);
} catch (IllegalStateException e) {
  if (e.getMessage() != null && e.getMessage().contains("only one top-level value")) {
    // cannot recover mid-stream safely; this is a design error
    throw new IllegalStateException("Wrap values in an array or use Strictness.LENIENT", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: Writing two literals (e.g. value("a"); value("b");) at document top level with default strictness; calling beginArray() after a complete top-level object; reusing a single JsonWriter to emit multiple independent JSON values without wrapping them in an array; loops that write multiple values forgetting to open a container first.

Common situations: Streaming multiple records to one writer (log/event streams) where each record is its own JSON object on the same writer; migrating from lenient to default strictness and suddenly hitting the limit; NDJSON-style emission attempted through a single default JsonWriter.

Related errors


AI-assisted analysis of google/gson@310ac341f2 (2026-08-10). Data as JSON: /api/errors/579fd59316f837dc. Report an issue: GitHub.

Appendix: source

Thrown at gson/src/main/java/com/google/gson/stream/JsonWriter.java:810

    if (context == NONEMPTY_OBJECT) { // first in object
      out.write(formattedComma);
    } else if (context != EMPTY_OBJECT) { // not in an object!
      throw new IllegalStateException("Nesting problem.");
    }
    newline();
    replaceTop(DANGLING_NAME);
  }

  /**
   * Inserts any necessary separators and whitespace before a literal value, inline array, or inline
   * object. Also adjusts the stack to expect either a closing bracket or another element.
   */
  @SuppressWarnings("fallthrough")
  private void beforeValue() throws IOException {
    switch (peek()) {
      case NONEMPTY_DOCUMENT:
        if (strictness != Strictness.LENIENT) {
          throw new IllegalStateException("JSON must have only one top-level value.");
        }
      // fall-through
      case EMPTY_DOCUMENT: // first in document
        replaceTop(NONEMPTY_DOCUMENT);
        break;

      case EMPTY_ARRAY: // first in array
        replaceTop(NONEMPTY_ARRAY);
        newline();
        break;

      case NONEMPTY_ARRAY: // another in array
        out.append(formattedComma);
        newline();
        break;

      case DANGLING_NAME: // value for name
        out.append(formattedColon);

View on GitHub (pinned to 310ac341f2)