google/gson · error · IllegalStateException

Did not expect a name

Error message

Did not expect a name

What it means

Thrown by JsonTreeWriter.name() when the stack is empty (no container is open) or when pendingName is already non-null (a previous name() has not yet been consumed by a value). JSON object members must alternate name then value; a second name without an intervening value, or a name with no enclosing object, is illegal.

Source

Thrown at gson/src/main/java/com/google/gson/internal/bind/JsonTreeWriter.java:150

  @Override
  public JsonWriter endObject() throws IOException {
    if (stack.isEmpty() || pendingName != null) {
      throw new IllegalStateException();
    }
    JsonElement element = peek();
    if (element instanceof JsonObject) {
      stack.remove(stack.size() - 1);
      return this;
    }
    throw new IllegalStateException();
  }

  @CanIgnoreReturnValue
  @Override
  public JsonWriter name(String name) throws IOException {
    Objects.requireNonNull(name, "name == null");
    if (stack.isEmpty() || pendingName != null) {
      throw new IllegalStateException("Did not expect a name");
    }
    JsonElement element = peek();
    if (element instanceof JsonObject) {
      pendingName = name;
      return this;
    }
    throw new IllegalStateException("Please begin an object before writing a name.");
  }

  @CanIgnoreReturnValue
  @Override
  public JsonWriter value(String value) throws IOException {
    if (value == null) {
      return nullValue();
    }
    put(new JsonPrimitive(value));
    return this;
  }

View on GitHub (pinned to 8b8628c656)

Solutions

  1. Ensure exactly one value (or container) follows every name() before the next name().
  2. Wrap name()+value() in a helper that always pairs them, so a skip can't leave a dangling name.
  3. Only call name() between beginObject() and endObject(), never at top level.
  4. Add a test exercising conditional fields to catch the dangling-name path.

Example fix

// before: two name() calls in a row
out.beginObject();
out.name("a");
out.name("b").value(2); // throws: Did not expect a name
out.endObject();

// after: pair each name with a value
out.beginObject();
out.name("a").value(1);
out.name("b").value(2);
out.endObject();
Defensive patterns

Strategy: validation

Validate before calling

// Only call name() inside an open object, and only when no name is pending
if (stackEmpty || pendingName != null) throw new IllegalStateException("name() not allowed here");
out.name(k);

Try / catch

try {
  out.name(k);
} catch (IllegalStateException e) {
  if ("Did not expect a name".equals(e.getMessage())) {
    // likely: double name() or name() outside object; write a null value to recover
    out.nullValue();
    out.name(k);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling name() twice in a row inside an object without writing a value between them; calling name() before any beginObject(); calling name() on a writer that just consumed a value and is back to expecting a name but pendingName is somehow still set (double-name bug).

Common situations: Serializer loop that emits name() then conditionally skips the value but still advances; copy-paste leaving two name() calls adjacent; serializer that writes name() at the top level instead of inside beginObject()/endObject().

Related errors


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