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

  1. Ensure the JSON object contains either {"id":"America/New_York"} or {"totalSeconds":3600}.
  2. Register a custom TypeAdapter<ZoneId> that accepts the producer's representation (e.g. a bare string).
  3. Validate the JSON shape (presence of id/totalSeconds) before calling fromJson.
  4. 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

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


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)