apache/shenyu · error · JsonParseException
The date should be a string value
Error message
The date should be a string value
What it means
GsonUtils' Timestamp deserializer expects the JSON value to be a JsonPrimitive string formatted as 'yyyy-MM-dd HH:mm:ss'. If the JSON element is not a primitive (object, array, null) it throws JsonParseException with this message, because only a string can be parsed into a Timestamp.
Solutions
- Send the date as a quoted string in 'yyyy-MM-dd HH:mm:ss' format.
- Make the target field String or a wrapper type and parse manually if the shape can vary.
- Register a custom deserializer (or use GsonUtils' built-in one) that handles null/object shapes defensively.
- Validate the JSON payload before deserialization.
Example fix
// before
{"createdAt": null}
// after
{"createdAt": "2026-09-12 10:15:30"} Defensive patterns
Strategy: validation
Validate before calling
JsonElement el = obj.get("createdAt"); if (el == null || !el.isJsonPrimitive() || !el.getAsJsonPrimitive().isString()) throw new IllegalArgumentException("createdAt must be 'yyyy-MM-dd HH:mm:ss' string"); Type guard
boolean isTimestampString(JsonElement el) { return el != null && el.isJsonPrimitive() && el.getAsJsonPrimitive().isString(); } Try / catch
try { Timestamp ts = gson.fromJson(json, Target.class); } catch (JsonParseException e) { LOG.error("bad timestamp payload", e); } Prevention
- Always serialize timestamps as quoted 'yyyy-MM-dd HH:mm:ss' strings
- Contract-test payloads against deserialization
- Use GsonUtils registered serializers consistently on both sides
- Reject null date fields at API validation layer
When it happens
Trigger: Deserializing JSON where a field mapped to java.sql.Timestamp is a JSON object/array/null instead of a quoted string, e.g. {"createdAt": {}} or {"createdAt": null} passed through GsonUtils.fromJson with a Timestamp-typed target.
Common situations: Upstream API changed a date field from string to object; sending JSON with null Timestamp field; hand-written JSON forgetting quotes around the date; GraphQL-ish nested payloads fed into admin DTOs.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- Invalid Swagger JSON format:
- Invalid request body: expected a JSON object
- Failed to parse request body into ChatCompletionRequest
- Invalid input JSON format
- Invalid JSON format
AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12).
Data as JSON: /api/errors/296ab7d3bd3736d6.
Report an issue: GitHub.
Appendix: source
Thrown at shenyu-common/src/main/java/org/apache/shenyu/common/utils/GsonUtils.java:515
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
return Duration.parse(reader.nextString());
} catch (IOException e) {
throw new ShenyuException(e);
}
}
}
private static class TimestampTypeAdapter implements JsonSerializer<Timestamp>, JsonDeserializer<Timestamp> {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public Timestamp deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
if (!(json instanceof JsonPrimitive)) {
throw new JsonParseException("The date should be a string value");
}
try {
LocalDateTime dateTime = FORMATTER.parse(json.getAsString(), LocalDateTime::from);
return Timestamp.valueOf(dateTime);
} catch (Exception e) {
throw new JsonParseException(e);
}
}
@Override
public JsonElement serialize(final Timestamp src, final Type typeOfSrc, final JsonSerializationContext context) {
LocalDateTime ldt = LocalDateTime.ofInstant(src.toInstant(), ZoneId.systemDefault());
String formatted = FORMATTER.format(ldt);
return new JsonPrimitive(formatted);
}
}
}
View on GitHub (pinned to 567142e072)