google/gson · error · IllegalArgumentException

No time zone indicator

Error message

No time zone indicator

What it means

Thrown by Gson's ISO8601Utils.parse when a date string that contains a time component ('T') has no trailing timezone indicator. After parsing year/month/day/[hour/min/sec], the parser checks position; if the string ends without a 'Z' or +/- offset, the date is considered incomplete and IllegalArgumentException("No time zone indicator") is thrown, which ISO8601Utils rewraps into a ParseException.

Solutions

  1. Ensure the producer appends 'Z' (for UTC) or an explicit offset like '+00:00' to every date-time string.
  2. Register a custom TypeAdapter<Date> that uses SimpleDateFormat with a lenient pattern matching zoneless strings and a default TimeZone.
  3. If you cannot change the producer, pre-process the JSON to append a default zone before parsing.
  4. Switch the target field to java.time.LocalDateTime (with a custom adapter) if the value is genuinely zoneless.

Example fix

// before
public class Event { public Date at; }
// JSON: {"at":"2020-01-01T12:00:00"} -> 'No time zone indicator'

// after: producer emits UTC zone indicator
// {"at":"2020-01-01T12:00:00Z"}

// or custom adapter accepting zoneless as UTC:
Gson g = new GsonBuilder()
  .registerTypeAdapter(Date.class, new TypeAdapter<Date>() {
    private final DateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    { f.setTimeZone(TimeZone.getTimeZone("UTC")); }
    public Date read(JsonReader in) throws IOException {
      try { return f.parse(in.nextString()); }
      catch (ParseException e) { throw new JsonSyntaxException(e); }
    }
    public void write(JsonWriter out, Date v) throws IOException { out.value(f.format(v)); }
  }).create();
Defensive patterns

Strategy: validation

Validate before calling

private static final Pattern ISO_Z =
  Pattern.compile("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$");
String raw = jsonNode.get("at").getAsString();
if (raw != null && !ISO_Z.matcher(raw).matches()) {
  // append 'Z' if the producer omits zone, or reject
  raw = raw + "Z";
}

Try / catch

try {
  return gson.fromJson(json, Event.class);
} catch (JsonSyntaxException e) {
  if (e.getCause() instanceof ParseException && e.getCause().getMessage().contains("No time zone indicator")) {
    // append default zone and retry once
    JsonObject patched = jsonNode.deepCopy();
    patched.addProperty("at", patched.get("at").getAsString() + "Z");
    return gson.fromJson(patched, Event.class);
  }
  throw e;
}

Prevention

When it happens

Trigger: Parsing a date-time string in ISO8601 form missing the timezone, e.g. "2020-01-01T12:00:00" (no trailing Z or offset), or "2020-01-01T12:00". This occurs when Gson deserializes into java.util.Date / java.util.Calendar using the default ISO format and the input omits the zone.

Common situations: Backends emitting 'local' datetimes without zone; databases returning TIMESTAMP without timezone; legacy formats like "yyyy-MM-dd'T'HH:mm:ss" produced by older Jackson/Joda configs; mismatches between producer (no zone) and Gson consumer (expects zone).

Related errors


AI-assisted analysis of google/gson@310ac341f2 (2026-08-10). Data as JSON: /api/errors/259e4b186f2efdf8. Report an issue: GitHub.

Appendix: source

Thrown at gson/src/main/java/com/google/gson/internal/bind/util/ISO8601Utils.java:231

              switch (parseEndOffset - offset) { // number of digits parsed
                case 2:
                  milliseconds = fraction * 10;
                  break;
                case 1:
                  milliseconds = fraction * 100;
                  break;
                default:
                  milliseconds = fraction;
              }
              offset = endOffset;
            }
          }
        }
      }

      // extract timezone
      if (date.length() <= offset) {
        throw new IllegalArgumentException("No time zone indicator");
      }

      TimeZone timezone = null;
      char timezoneIndicator = date.charAt(offset);

      if (timezoneIndicator == 'Z') {
        timezone = TIMEZONE_UTC;
        offset += 1;
      } else if (timezoneIndicator == '+' || timezoneIndicator == '-') {
        String timezoneOffset = date.substring(offset);

        // When timezone has no minutes, we should append it, valid timezones are, for example:
        // +00:00, +0000 and +00
        timezoneOffset = timezoneOffset.length() >= 5 ? timezoneOffset : timezoneOffset + "00";

        offset += timezoneOffset.length();
        // 18-Jun-2015, tatu: Minor simplification, skip offset of "+0000"/"+00:00"
        if (timezoneOffset.equals("+0000") || timezoneOffset.equals("+00:00")) {

View on GitHub (pinned to 310ac341f2)