{"record":{"id":"ed5f946b86330a5c","repo":"google/gson","slug":"expected-string-but-was-token-at-path-path","errorCode":null,"errorMessage":"Expected STRING but was ${token} at path ${path}","messagePattern":"Expected STRING but was (.+?) at path (.+?)","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/310ac341f2f92a454b229bf21f70d2d18b2b6db7/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java#L192-L228","documentation":"JsonTreeReader.nextString accepts only STRING or NUMBER tokens; anything else throws IllegalStateException with the actual token and path. Used internally by adapters that call nextString expecting text.","triggerScenarios":"An adapter calls nextString() (often via a String-typed field) but the JSON value is a boolean, null, object, or array. Typical when a field declared as String actually receives a non-string literal.","commonSituations":"Producer changing a string field to a boolean/number; loose typing where a String field receives 'true'; deserializing JsonElement-backed trees where a primitive is not a string.","solutions":["Make the producer emit a string for that field (\"true\" rather than true).","Register a custom TypeAdapter that reads the raw token regardless of type.","Use a more tolerant reader (setLenient) only if the data genuinely varies, but prefer fixing the schema."],"exampleFix":"// before: {\"flag\":true} into String field\nGson().fromJson(\"{\\\"flag\\\":true}\", FlagHolder::class.java) // Expected STRING but was BOOLEAN\n\n// after: {\"flag\":\"true\"}\nGson().fromJson(\"{\\\"flag\\\":\\\"true\\\"}\", FlagHolder::class.java)","handlingStrategy":"validation","validationCode":"// Inspect token before nextString\nJsonToken t = reader.peek();\nif (t == STRING || t == NUMBER) {\n  String s = reader.nextString();\n} else {\n  reader.skipValue(); // or handle differently\n}","typeGuard":null,"tryCatchPattern":"try {\n  reader.nextString();\n} catch (IllegalStateException e) {\n  if (e.getMessage().startsWith(\"Expected STRING\")) {\n    // coerce or skip the non-string token\n    reader.skipValue();\n  } else throw e;\n}","preventionTips":["Keep String fields as JSON strings on the producer side.","Register a tolerant TypeAdapter for fields that may receive other types.","Peek before consuming when input shape is not guaranteed."],"tags":["json-tree-reader","token","type-mismatch","string"],"backgroundTag":null,"analyzedSha":"310ac341f2f92a454b229bf21f70d2d18b2b6db7","analyzedAt":"2026-08-10T02:58:47.455Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}