- Add serialization support for LocalDate, LocalDateTime, LocalTime, ZonedDateTime, OffsetDateTime, and Instant in DefaultJSONWriter - Add deserialization support for the same types in JSONPopulator - Support @JSON(format=...) custom formats for all temporal types - Fix Instant custom-format serialization requiring UTC zone - Add Calendar serialization/deserialization via temporal bridge - Add comprehensive tests for all temporal types including custom formats, malformed input, and null handling Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
7.3 KiB
date, topic, tags, status, git_commit
| date | topic | tags | status | git_commit | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-02-27T12:00:00+01:00 | WW-4428: Add java.time (LocalDate, LocalDateTime) support to JSON plugin |
|
complete | 4d2eb93835 |
Research: WW-4428 — Java 8 Date/Time Support in JSON Plugin
Date: 2026-02-27
Research Question
What is the current state of Java 8 java.time support (LocalDate, LocalDateTime, etc.) in the Struts JSON plugin, and what changes are needed to implement WW-4428?
Summary
The JSON plugin has zero java.time support. Only java.util.Date and java.util.Calendar are handled. Java 8 date types like LocalDate and LocalDateTime fall through to JavaBean introspection during serialization (producing garbage like {"dayOfMonth":23,"month":"DECEMBER",...}) and throw exceptions during deserialization. The core module already has comprehensive java.time support via DateConverter, but none of it is wired into the JSON plugin.
Detailed Findings
1. Serialization — DefaultJSONWriter
File: plugins/json/src/main/java/org/apache/struts2/json/DefaultJSONWriter.java
The process() method (line ~163) dispatches on type:
} else if (object instanceof Date) {
this.date((Date) object, method);
} else if (object instanceof Calendar) {
this.date(((Calendar) object).getTime(), method);
}
There is no branch for java.time.temporal.TemporalAccessor or any specific java.time type. These objects fall through to processCustom() → bean(), which introspects them as JavaBeans.
The date() method (line ~335) only accepts java.util.Date and uses SimpleDateFormat:
protected void date(Date date, Method method) {
// uses SimpleDateFormat with JSONUtil.RFC3339_FORMAT default
}
The setDateFormatter() method (line ~487) only creates a SimpleDateFormat.
2. Deserialization — JSONPopulator
File: plugins/json/src/main/java/org/apache/struts2/json/JSONPopulator.java
isJSONPrimitive() (line ~92) only recognizes Date.class:
return clazz.isPrimitive() || clazz.equals(String.class) || clazz.equals(Date.class) ...
convertPrimitive() (line ~255) only handles Date.class via SimpleDateFormat.parse(). Java.time types will throw JSONException("Incompatible types for property ...").
3. Format Configuration
File: plugins/json/src/main/java/org/apache/struts2/json/JSONUtil.java
RFC3339_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"(line 53) — the default format- The java.time equivalent is
DateTimeFormatter.ISO_LOCAL_DATE_TIME
4. @JSON Annotation
File: plugins/json/src/main/java/org/apache/struts2/json/annotations/JSON.java
Has format() attribute for per-property date format overrides — currently only used with SimpleDateFormat. Should be extended to work with DateTimeFormatter for java.time types.
5. @JSONFieldBridge Workaround
Directory: plugins/json/src/main/java/org/apache/struts2/json/bridge/
The FieldBridge interface provides a manual escape hatch (objectToString) but only supports serialization, not deserialization.
6. Core Module Already Has java.time Support
File: core/src/main/java/org/apache/struts2/conversion/impl/DateConverter.java
Handles LocalDate, LocalDateTime, LocalTime, OffsetDateTime using DateTimeFormatter.parseBest(). This is not wired into the JSON plugin.
7. Test Coverage
DefaultJSONWriterTest.java— only testsjava.util.Dateserialization (lines 115-142)SingleDateBean.java— test fixture with only ajava.util.DatefieldJSONPopulatorTest.java— no dedicated date deserialization test- Zero tests for any java.time type
Gap Analysis
| Type | Serialization | Deserialization |
|---|---|---|
java.util.Date |
Supported | Supported |
java.util.Calendar |
Supported (→ Date) | Not supported |
java.time.LocalDate |
Not supported | Not supported |
java.time.LocalDateTime |
Not supported | Not supported |
java.time.LocalTime |
Not supported | Not supported |
java.time.ZonedDateTime |
Not supported | Not supported |
java.time.Instant |
Not supported | Not supported |
java.time.OffsetDateTime |
Not supported | Not supported |
Implementation Points
To implement WW-4428, changes are needed in:
DefaultJSONWriter.java
- Add
instanceofchecks inprocess()forLocalDate,LocalDateTime,LocalTime,ZonedDateTime,Instant,OffsetDateTime(or a blanketTemporalAccessorcheck) - Add a new
temporal(TemporalAccessor, Method)method usingDateTimeFormatter - Use sensible defaults:
ISO_LOCAL_DATEforLocalDate,ISO_LOCAL_DATE_TIMEforLocalDateTime, etc. - Respect
@JSON(format=...)annotation viaDateTimeFormatter.ofPattern()
JSONPopulator.java
- Extend
isJSONPrimitive()to recognize java.time classes - Extend
convertPrimitive()to parse java.time types from strings usingDateTimeFormatter - Respect
@JSON(format=...)for custom formats
JSONWriter.java (interface)
- Consider adding
setDateTimeFormatter(String)or reusingsetDateFormatter()for both legacy and java.time
Tests
- Add test beans with java.time fields
- Add serialization tests for each supported java.time type
- Add deserialization tests for each type
- Test
@JSON(format=...)with java.time types - Test default format behavior
Architecture Insights
- The JSON plugin was designed when Java 6 was the target, hence
SimpleDateFormatthroughout - The
@JSON(format=...)annotation is the natural extension point for per-field formatting - The core module's
DateConvertershows the established pattern for handling java.time in Struts - Since Struts now requires Java 17+, there are no compatibility concerns with using java.time directly
Historical Context
- WW-4428 was filed in December 2014 (Struts 2.3.20 era, targeting Java 6/7)
- Original constraint: couldn't add java.time directly due to Java 6/7 compatibility
- Related ticket: WW-5016 — Support java 8 date time in the date tag (already implemented in
components/Date.java) - No prior research documents in thoughts/ for this topic
Related Research
None found in thoughts/shared/research/.
Open Questions
- Should
Instantbe serialized as epoch millis (number) or ISO-8601 string? - Should
ZonedDateTimeinclude the zone info in the default format? - Should the implementation use a blanket
TemporalAccessorcheck or individual type checks? - Should
Calendardeserialization also be added while we're at it?