google/gson · error · JsonSyntaxException
Missing id or totalSeconds field; at path
Error message
Missing id or totalSeconds field; at path ${path} What it means
Gson's built-in ZoneId adapter reads an object expecting exactly one of two discriminant fields: "id" (for region-based zones like 'America/New_York') or "totalSeconds" (for ZoneOffset). If the object contains neither, deserialization cannot proceed and throws JsonSyntaxException. This mirrors the legacy reflective serialization of java.time.ZoneId from JDK 21.
Solutions
- Ensure the JSON object contains either {"id":"America/New_York"} or {"totalSeconds":3600}.
- Register a custom TypeAdapter<ZoneId> that accepts the producer's representation (e.g. a bare string).
- Validate the JSON shape (presence of id/totalSeconds) before calling fromJson.
- Switch the producer to emit Gson-compatible objects.
Example fix
// before: {}
Gson().fromJson("{}", ZoneId::class.java) // throws
// after
Gson().fromJson("{\"id\":\"America/New_York\"}", ZoneId::class.java) Defensive patterns
Strategy: validation
Validate before calling
// Validate the ZoneId object shape before deserializing
static boolean isValidZoneIdJson(JsonObject o) {
return o.has("id") || o.has("totalSeconds");
}
// Or register a custom adapter accepting a bare string
class ZoneIdAdapter extends TypeAdapter<ZoneId> {
public void write(JsonWriter out, ZoneId v) throws IOException { out.value(v.getId()); }
public ZoneId read(JsonReader in) throws IOException { return ZoneId.of(in.nextString()); }
} Try / catch
try {
gson.fromJson(json, ZoneId.class);
} catch (JsonSyntaxException e) {
if (e.getMessage().startsWith("Missing id or totalSeconds")) {
// producer emitted a different ZoneId shape; switch adapter
log.warn("ZoneId JSON missing id/totalSeconds: {}", e.getMessage());
}
throw e;
} Prevention
- Produce ZoneId as a string id and register a custom adapter for cross-system interop.
- Validate object shape before deserializing java.time zones.
- Document Gson's expected ZoneId encoding for producers.
- Pin Gson version to avoid encoding drift.
When it happens
Trigger: Deserializing a ZoneId/ZoneOffset from a JSON object such as "{}" or "{\"foo\":1}" that lacks both 'id' and 'totalSeconds'. Occurs when the JSON was produced by a non-Gson serializer or hand-edited.
Common situations: Interoperating with systems that serialize ZoneId as a plain string instead of an object; upgrading from an older Gson whose reflective encoding differed; payloads trimmed for size.
Related errors
- Missing field; at path
- Array must have size 1, but has size
- Attempted to deserialize a java.lang.Class. Forgot to…
- Cannot allocate . Usage of JDK sun.misc.Unsafe is enabled…
- cannot deserialize because it does not define a field named
AI-assisted analysis of google/gson@310ac341f2 (2026-08-10).
Data as JSON: /api/errors/5980e6d9308525b6.
Report an issue: GitHub.
Appendix: source
Thrown at gson/src/main/java/com/google/gson/internal/bind/JavaTimeTypeAdapters.java:319
switch (name) {
case "id":
id = in.nextString();
break;
case "totalSeconds":
totalSeconds = in.nextInt();
break;
default:
// Ignore other fields.
in.skipValue();
}
}
in.endObject();
if (id != null) {
return ZoneId.of(id);
} else if (totalSeconds != null) {
return ZoneOffset.ofTotalSeconds(totalSeconds);
} else {
throw new JsonSyntaxException(
"Missing id or totalSeconds field; at path " + in.getPreviousPath());
}
}
@Override
public void write(JsonWriter out, ZoneId value) throws IOException {
if (value instanceof ZoneOffset) {
out.beginObject();
out.name("totalSeconds");
out.value(((ZoneOffset) value).getTotalSeconds());
out.endObject();
} else {
out.beginObject();
out.name("id");
out.value(value.getId());
out.endObject();
}
}View on GitHub (pinned to 310ac341f2)