-
Notifications
You must be signed in to change notification settings - Fork 2
JAXB type adapter classes for java.time
The library includes classes intended for use with JAXB.
Working with date and time values in JAXB can be cumbersome because you are
(normally) forced to work with XMLGregorianCalendar
. However, there is a
simple solution for that: make JAXB use Java’s OffsetDateTime
and
OffsetTime
instead. This library provides a set of XmlAdapters
to
achieve this goal:
XmlAdapter class |
Translates to/from |
|
---|---|---|
XML schema data type |
Java class |
|
|
|
|
|
|
|
|
|
|
|
|
|
It is outside the scope of this guide to explain how JAXB XmlAdapters are applied in general. But here’s a little taste:
public class Customer {
@XmlElement
@XmlJavaTypeAdapter(OffsetDateTimeXmlAdapter.class)
@XmlSchemaType(name="dateTime")
public OffsetDateTime getLastOrderTime() {
....
}
@XmlElement
@XmlJavaTypeAdapter(OffsetDateXmlAdapter.class)
@XmlSchemaType(name="date")
public OffsetDateTime getDateOfBirth() { // returns a date-only value
....
}
}
The trick is the XmlJavaTypeAdapter annotation which can be applied to fields, getters/setters, packages, etc.
In fact the most convenient usage of this library is probably to apply the
annotations at the package level. Thereby you do not have to annotate each element/attribute
individually. Simply put something like the following in your package-info.java
file:
@XmlJavaTypeAdapters
({
@XmlJavaTypeAdapter(value=OffsetDateTimeXmlAdapter.class, type=OffsetDateTime.class),
@XmlJavaTypeAdapter(value=OffsetTimeXmlAdapter.class, type=OffsetTime.class),
@XmlJavaTypeAdapter(value=OffsetDateClassXmlAdapter.class, type=OffsetDate.class),
})
@XmlSchemaTypes
({
@XmlSchemaType(name="dateTime", type=OffsetDateTime.class),
@XmlSchemaType(name="time", type=OffsetTime.class),
@XmlSchemaType(name="date", type=OffsetDate.class)
})
package org.example.mydtopackage;
import com.addicticks.texttime.jaxb.OffsetDate;
import com.addicticks.texttime.jaxb.OffsetDateTimeXmlAdapter;
import com.addicticks.texttime.jaxb.OffsetDateClassXmlAdapter;
import com.addicticks.texttime.jaxb.OffsetTimeXmlAdapter;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import javax.xml.bind.annotation.XmlSchemaType; /* or jakarta namespace */
import javax.xml.bind.annotation.XmlSchemaTypes; /* or jakarta namespace */
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; /* or jakarta namespace */
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters; /* or jakarta namespace */
The adapter will now apply to all classes in the package.
Furthermore, because of the @XmlSchemaTypes
annotation, if you
generate an XML Schema from your classes, it will use correct schema types.
(otherwise you’ll see xs:string
as the schema type for such elements/attributes,
which is not what you want)
If you generate Java classes from XML schema using the xjc
tool then you must
use a so-called bindings file to instruct the xjc tool which adapter you want to use.
Using the out-of-the-box adapters from this library then the bindings file should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<!-- This file is automatically picked up by the jaxb2-maven-plugin
if it lives in src/main/xjb -->
<jxb:bindings
xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
version="2.1">
<jxb:globalBindings>
<!-- Avoid having to work with XMLGregorianCalendar.
Instead, map as follows:
XML dateTime : OffsetDateTime
XML date : OffsetDateTime (time value truncated)
XML time : OffsetTime -->
<xjc:javaType adapter="com.addicticks.texttime.jaxb.OffsetDateTimeXmlAdapter"
name="java.time.OffsetDateTime" xmlType="xs:dateTime"/>
<xjc:javaType adapter="com.addicticks.texttime.jaxb.OffsetDateXmlAdapter"
name="java.time.OffsetDateTime" xmlType="xs:date"/>
<xjc:javaType adapter="com.addicticks.texttime.jaxb.OffsetTimeXmlAdapter"
name="java.time.OffsetTime" xmlType="xs:time"/>
</jxb:globalBindings>
</jxb:bindings>
If you are using the JAXB2 Maven Plugin
then name the file jaxb-datetime-bindings.xjb
and place it in src/main/xjb
.
Thereby it will automatically be picked up by the Maven plugin.
In the next section you are presented with the differences between
the XML Schema Date Time format and the java.time
objects
and why conversion between the two worlds require careful consideration.
The major problem to tackle is that the authors of the XML Schema made the mistake of making the timezone offset optional. For this reason, the XmlAdapter must be able to parse a string value such as
2018-05-22T23:44:51
Out-of-the box, the adapters provide a best guess of what the timezone might be when not explicitly specified. The default implementation is to use the system’s default ZoneId and then find the offset for that Zone at the local time from the text. Say for example that the system’s default ZoneId is 'Europe/Paris'. The resulting timezone offset for the above text value would then be '+02:00' because Paris was using DST in May 2018.
If, for whatever reason, you are unhappy with the default implementation, then
you’ll need to subclass the XmlAdapter and override the getZoneOffsetFor…()
method.
For example you may want the default to always be UTC:
public class OffsetDateTimeXmlAdapterUTCDefault extends OffsetDateTimeXmlAdapter {
@Override
public ZoneOffset getZoneOffsetForDateTime(LocalDateTime localDateTime) {
return ZoneOffset.UTC;
}
}
…and then use the custom class name in your JAXB annotations or in the XJC bindings file.
The OffsetTime
and OffsetDateTime
classes introduced in Java 8 are
the closest equivalents from the java.time package to the XML Schema date
and time values. However, there are a few subtle differences:
-
The XML date/time data types allows for the timezone offset to be left out. Therefore, when unmarshalling, we may have to supply our own value for the offset. This value should depend entirely on the scenario. By default the library will supply the current offset from the JVM’s default
ZoneId
, but this may not be what you want. In this case then extend the adapters and override thegetZoneOffsetFor…()
method. -
Java doesn’t have a date-only data type. Two different adapters are provided for converting to/from
xs:date
:-
OffsetDateClassXmlAdapter
uses a customOffsetDate
class which is merely a thin wrapper around theOffsetDateTime
class from the JDK. This is likely to be the most convenient alternative if you want to apply the@XmlJavaTypeAdapter
annotation at the package level. -
OffsetDateXmlAdapter
uses the JDK’s ownOffsetDateTimeClass
with the time set to midnight. This is likely to be the most convenient alternative if you are using XJC to generate classes from XML schema.
-
-
The XML
xs:time
andxs:dateTime
data types allow for unlimited number of digits in the fractional part of the seconds element. For example the following is a perfectly validxs:time
value: "23:30:28.123456789012345678901234567890". In order to overcome this discrepancy while unmarshalling (parsing text into Java type), theOffsetDateTimeXmlAdapter
andOffsetTimeXmlAdapter
will silently truncate such excessive digits. While marshalling they never output more then 9 fractional digits. -
The XML
xs:time
andxs:dateTime
data types allow for the seconds field to use a range from 0 to 60, while the JSR-310 Java types ofOffsetDateTime
andOffsetTime
only allow the range 0 to 59. The difference is leap seconds. For example "2016-12-31T23:59:60Z" is actually a point in time because a leap second was added that year. The jTexttime library will fail in parsing such a value. However, in real life, it is very unlikely that a system will produce a value like that as most systems tend to spread out the leap second over the last part of the 31st of December or to use the value "23:59:59" twice. -
The XML
xs:time
andxs:dateTime
data types allow for a special midnight time value:24:00:00
. The following two values are the same according to the XSD specification:2022-02-28T24:00:00Z
and2022-03-01T00:00:00Z
. This library fully supports parsing this special midnight value during unmarshalling, however it will never marshal into this value but instead use the00:00:00
notation. It is the opinion of the author that support for the24:00:00
is an unnecessary complication in the XSD specification and that there is no purpose in making the situation worse for other consumers by ever producing such a value.