HMCL-dev/HMCL · error · JsonParseException

The instant should be a string value

Error message

The instant should be a string value

What it means

InstantTypeAdapter expects the incoming JSON element to be a JSON string (a primitive) that it can parse into a timestamp. This JsonParseException is thrown when the element is an object, array, number, boolean, or null instead of a string.

Solutions

  1. Serialize the Instant as a string (InstantTypeAdapter does this via toString/ISO format).
  2. Convert numeric epoch values to a string or to Instant before deserialization.
  3. If null is possible, declare the field as @Nullable Instant or use a null-safe TypeAdapter.
  4. Preprocess the JsonElement to a primitive string before handing it to Gson.

Example fix

// before
{"lastLogin": 1720000000000}
// after
{"lastLogin": "2024-07-03T09:06:40Z"}
Defensive patterns

Strategy: validation

Validate before calling

JsonElement el = JsonParser.parseString(json);
if (!(el instanceof JsonPrimitive p) || !p.isString())
    throw new IllegalArgumentException("Instant field must be a JSON string");

Type guard

static boolean isJsonStringInstant(JsonElement el) {
    return el instanceof JsonPrimitive p && p.isString();
}

Try / catch

try {
    return gson.fromJson(json, Instant.class);
} catch (JsonParseException e) {
    log.warn("Instant field was not a JSON string", e);
}

Prevention

When it happens

Trigger: Deserializing JSON where an Instant-typed field holds a number (epoch millis), a nested object, or null instead of a formatted date-time string.

Common situations: APIs or files that serialize timestamps as epoch milliseconds or ISO objects; JSON hand-edited to "lastLogin": 1720000000000 instead of a quoted string.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/7094bc6b8d3c4198. Report an issue: GitHub.

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/InstantTypeAdapter.java:75

    /// Deserializes a JSON string into an [Instant].
    ///
    /// @param json    the JSON element to deserialize
    /// @param type    the requested target type
    /// @param context the Gson deserialization context
    /// @return the parsed instant
    /// @throws JsonParseException     if `json` is not a string or cannot be parsed as an instant
    /// @throws IllegalArgumentException if `type` is not [Instant]
    @Override
    public Instant deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
        if (json instanceof JsonPrimitive) {
            Instant time = deserializeToInstant(json.getAsString());
            if (type == Instant.class)
                return time;
            else
                throw new IllegalArgumentException(this.getClass() + " cannot be deserialized to " + type);
        } else {
            throw new JsonParseException("The instant should be a string value");
        }
    }

    /// Formatter for the legacy US localized date-time representation.
    private static final DateTimeFormatter EN_US_FORMAT = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.MEDIUM)
            .withLocale(Locale.US)
            .withZone(ZoneId.systemDefault());

    /// Formatter for ISO local date-time text followed by one of the supported offset forms.
    private static final DateTimeFormatter ISO_DATE_TIME = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
            .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
            .optionalStart().appendOffset("+H:MM", "+0:00").optionalEnd()
            .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd()
            .optionalStart().appendOffset("+HH", "Z").optionalEnd()
            .optionalStart().appendOffsetId().optionalEnd()
            .toFormatter();

View on GitHub (pinned to 24702dc5a0)