{"id":"6a684f6f2d771efe","repo":"google/gson","slug":"expected-string-but-was-token-location","errorCode":null,"errorMessage":"Expected STRING but was {token}{location}","messagePattern":"Expected STRING but was (.+?)(.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java","lineNumber":210,"sourceCode":"    expect(JsonToken.NAME);\n    Iterator<?> i = (Iterator<?>) peekStack();\n    Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();\n    String result = (String) entry.getKey();\n    pathNames[stackSize - 1] = skipName ? \"<skipped>\" : result;\n    push(entry.getValue());\n    return result;\n  }\n\n  @Override\n  public String nextName() throws IOException {\n    return nextName(false);\n  }\n\n  @Override\n  public String nextString() throws IOException {\n    JsonToken token = peek();\n    if (token != JsonToken.STRING && token != JsonToken.NUMBER) {\n      throw new IllegalStateException(\n          \"Expected \" + JsonToken.STRING + \" but was \" + token + locationString());\n    }\n    String result = ((JsonPrimitive) popStack()).getAsString();\n    if (stackSize > 0) {\n      pathIndices[stackSize - 1]++;\n    }\n    return result;\n  }\n\n  @Override\n  public boolean nextBoolean() throws IOException {\n    expect(JsonToken.BOOLEAN);\n    boolean result = ((JsonPrimitive) popStack()).getAsBoolean();\n    if (stackSize > 0) {\n      pathIndices[stackSize - 1]++;\n    }\n    return result;\n  }","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/google/gson/blob/8b8628c65699bc4421696183c62ae0c1b9b281dc/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java#L192-L228","documentation":"JsonTreeReader.nextString throws IllegalStateException('Expected STRING but was ' + token + locationString) when the current token is neither STRING nor NUMBER. nextString accepts NUMBER as a convenience (it stringifies the number), but rejects BOOLEAN, NULL, NAME, BEGIN_*, END_*. The {token} is the actual JsonToken, {location} the path.","triggerScenarios":"Calling nextString() on a JsonReader/JsonTreeReader positioned at a boolean, null, object, or array. Common in custom adapters or when deserializing a field whose value type changed from string to object/null.","commonSituations":"Field that was a string is now an object or null in a new API version; union types where the value can be a string or a structured object; lenient inputs like 'true'/'false' expected as strings.","solutions":["In custom adapters, switch on in.peek() and only call nextString() when token is STRING (or NUMBER).","Handle NULL with in.nextNull(), and other shapes explicitly, before reading a string.","Align the producer to emit the field as a string consistently.","Use Gson.getAdapter(JsonElement.class).read(in) to capture the value as a tree, then inspect its type safely."],"exampleFix":"// before\nString name = in.nextString(); // throws if value is null or boolean\n\n// after\nString name;\nif (in.peek() == JsonToken.NULL) { in.nextNull(); name = null; }\nelse if (in.peek() == JsonToken.STRING) { name = in.nextString(); }\nelse { throw new JsonSyntaxException(\"Expected name string at \" + in.getPath()); }","handlingStrategy":"type-guard","validationCode":"JsonToken t = reader.peek();\nif (t != JsonToken.STRING && t != JsonToken.NUMBER) {\n  throw new IllegalStateException(\"Expected string at \" + reader.getPath() + \", got \" + t);\n}\nString s = reader.nextString();","typeGuard":"static boolean isStringOrNumberToken(JsonReader r) throws IOException {\n  JsonToken t = r.peek();\n  return t == JsonToken.STRING || t == JsonToken.NUMBER;\n}","tryCatchPattern":"try {\n  return reader.nextString();\n} catch (IllegalStateException e) {\n  if (e.getMessage() != null && e.getMessage().startsWith(\"Expected STRING\")) {\n    throw new InvalidPayloadException(\"Expected string field at \" + reader.getPath(), e);\n  }\n  throw e;\n}","preventionTips":["Handle NULL and other token types explicitly before calling nextString().","Pin the data contract for string fields (always a string, or document nullable).","Prefer reading into JsonElement and inspecting when the shape varies."],"tags":["jsonreader","json-tree","string","type-adapter"],"analyzedSha":"8b8628c65699bc4421696183c62ae0c1b9b281dc","analyzedAt":"2026-08-04T19:12:22.202Z","schemaVersion":2}