Using Java 8 Time within JAXB with XJC
21 Sep 2017 - Source - CommentsWhen we left off on our Gradle JAXB project, we were using a simple Gradle configuration to generate Java classes from an XML schema using JAXB and XJC. However, once you start to use the generated classes, you will notice something about date fields that might be difficult to use. Once again, we are using the ISO 20022 schemas in the sample.
If you don’t want to follow the entire tutorial, you can jump right to the complete code on GitHub.
Before we begin, let’s take a look at the issue. Notice the FinancialInstrumentAttributes79
class:
public class FinancialInstrumentAttributes79 {
@XmlElement(name = "FinInstrmId", required = true)
protected SecurityIdentification19 finInstrmId;
@XmlElement(name = "PlcOfListg")
protected MarketIdentification3Choice plcOfListg;
@XmlElement(name = "DayCntBsis")
protected InterestComputationMethodFormat4Choice dayCntBsis;
@XmlElement(name = "ClssfctnTp")
protected ClassificationType32Choice clssfctnTp;
@XmlElement(name = "OptnStyle")
protected OptionStyle8Choice optnStyle;
@XmlElement(name = "DnmtnCcy")
protected String dnmtnCcy;
@XmlElement(name = "NxtCpnDt")
protected XMLGregorianCalendar nxtCpnDt;
@XmlElement(name = "XpryDt")
protected XMLGregorianCalendar xpryDt;
@XmlElement(name = "FltgRateFxgDt")
protected XMLGregorianCalendar fltgRateFxgDt;
@XmlElement(name = "MtrtyDt")
protected XMLGregorianCalendar mtrtyDt;
@XmlElement(name = "IsseDt")
protected XMLGregorianCalendar isseDt;
...
}
While some of those fields use simple strings, the default XMLGregorianCalendar
class is not the easiest to work with. Some of these fields do not even store time information and would be much better suited with the Java 8 LocalDate
class. Luckily, with a bit of xml configuration in the bindings file, we can tell XJC to use type adapters for Java 8’s Date and Time API (aka JSR-310).
Getting Started
At its core, JAXB uses the XmlAdapter<ValueType,BoundType>
class to convert between the XML string and the Java class. We could extend this class ourselves but I found an open source project jaxb-java-time-adapters that did the job quite well. To start, we will need to add this dependency to gradle so that jaxb can use the classes:
dependencies {
compile(files(genJaxb.classesDir).builtBy(genJaxb))
jaxb "com.sun.xml.bind:jaxb-xjc:2.1.7"
jaxb "com.migesok:jaxb-java-time-adapters:1.1.3"
}
Next, we will need to add the xjc namespace to the top of the binding.xml
file:
<bindings xmlns="http://java.sun.com/xml/ns/jaxb" version="2.1"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc">
...
</bindings>
However, you’re not done yet. If you were to try to run gradle clean genJaxb
right now, you would see an error similar to the following:
[ant:xjc] [ERROR] vendor extension bindings (jaxb:extensionBindingPrefixes) are not allowed in the strict mode. Use -extension.
The error is pretty self-explanatory but it can be difficult to act on if you don’t know where to look. We just need to add one simple line to the xjc ant configuration in the build.gradle
file:
xjc(destdir: sourcesDir, binding: "${projectDir}/src/main/resources/binding.xml") {
schema(dir: "${projectDir}/src/main/resources", includes: '**/*.xsd')
arg(value: "-extension")
produces(dir: sourcesDir, includes: '**/*.java')
}
Adding the Type Adapter
After adding the dependency and enabling extension bindings, we can configure our new type adapters. Just add the following to the top of the binding.xml
file:
<bindings xmlns="http://java.sun.com/xml/ns/jaxb" version="2.1"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc">
<globalBindings>
<xjc:javaType name="java.time.LocalDate" xmlType="xs:date"
adapter="com.migesok.jaxb.adapter.javatime.LocalDateXmlAdapter" />
<xjc:javaType name="java.time.LocalDateTime" xmlType="xs:dateTime"
adapter="com.migesok.jaxb.adapter.javatime.LocalDateTimeXmlAdapter" />
</globalBindings>
...
</bindings>
We’re just going to configure the adapters for LocalDate
and LocalDateTime
(binding them to the xs:date
and xs:dateTime
XML types) but you can add as many as you would like.
Result
Now let’s look back at that same FinancialInstrumentAttributes79
class from above:
public class FinancialInstrumentAttributes79 {
@XmlElement(name = "FinInstrmId", required = true)
protected SecurityIdentification19 finInstrmId;
@XmlElement(name = "PlcOfListg")
protected MarketIdentification3Choice plcOfListg;
@XmlElement(name = "DayCntBsis")
protected InterestComputationMethodFormat4Choice dayCntBsis;
@XmlElement(name = "ClssfctnTp")
protected ClassificationType32Choice clssfctnTp;
@XmlElement(name = "OptnStyle")
protected OptionStyle8Choice optnStyle;
@XmlElement(name = "DnmtnCcy")
protected String dnmtnCcy;
@XmlElement(name = "NxtCpnDt", type = String.class)
@XmlJavaTypeAdapter(LocalDateXmlAdapter.class)
protected LocalDate nxtCpnDt;
@XmlElement(name = "XpryDt", type = String.class)
@XmlJavaTypeAdapter(LocalDateXmlAdapter.class)
protected LocalDate xpryDt;
@XmlElement(name = "FltgRateFxgDt", type = String.class)
@XmlJavaTypeAdapter(LocalDateXmlAdapter.class)
protected LocalDate fltgRateFxgDt;
@XmlElement(name = "MtrtyDt", type = String.class)
@XmlJavaTypeAdapter(LocalDateXmlAdapter.class)
protected LocalDate mtrtyDt;
@XmlElement(name = "IsseDt", type = String.class)
@XmlJavaTypeAdapter(LocalDateXmlAdapter.class)
protected LocalDate isseDt;
...
}
Now the generated classes are using LocalDate
instead of XMLGregorianCalendar
for these dates. Notice the @XMLJavaType
annotation on the field–that is how the JAXB marshaller knows how to marshal and unmarshal the XML and these generated classes.