Jaxb Bindings for java.time (LocalDate, LocalDateTime)

If you deal with legacy applications, especially those handling XML JMS messages, then at one point you will encounter the xml DataTypes (date/datetime).

As java.util.Date and java.util.Calender are considered legacy; we should aim to use java.time package instead.

For that we need to override the specified default bindings of XML Schema built-in datatypes by customizing JAXB Bindings.

We will cover how to achieve this by using legacy jaxb:bindings version=1 and jaxb:bindings version=2.1


Version 1: Define parseDate, printDate, parseDateTime, printDateTime methods in a single class.

The class will not extend/override anything. Its purpose is to convert between String to various java.time DateTypes and vice-versa.

public final class DateTypeConverter {

    public static LocalDate parseDate(String inputDate) {
        return inputDate != null ? DateTimeFormatter.ISO_DATE.parse(inputDate, LocalDate::from) : null;
    }

    public static String printDate(LocalDate inputDate) {
        return inputDate != null ? DateTimeFormatter.ISO_DATE.format(inputDate) : null;
    }

    public static LocalDateTime parseDateTime(String inputDate)  {
        return inputDate != null ? DateTimeFormatter.ofPattern("yyyy-MM-ddTHH.mm.ss.SSS").parse(inputDate, LocalDateTime::from) : null;
    }

    public static String printDateTime(LocalDateTime inputDate) {
        return inputDate != null ? DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(inputDate) : null;
    }

}

Define a bindings.xjb file and <jaxb:globalBindings> section. The global bindings should look like below:

<?xml version="1.0"?>
<jaxb:bindings version="1.0" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
  jaxb:extensionBindingPrefixes="xjc" xmlns:annox="http://annox.dev.java.net" xmlns:inheritance="http://jaxb2-commons.dev.java.net/basic/inheritance" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <jaxb:globalBindings typesafeEnumMemberName="generateName">
    <jaxb:javaType name="java.time.LocalDateTime" xmlType="xs:dateTime" parseMethod="com.hello.adapter.border.util.DateTypeConverter.parseDateTime" printMethod="com.hello.adapter.border.util.DateTypeConverter.printDateTime" />
    <jaxb:javaType name="java.time.LocalDate" xmlType="xs:date" parseMethod="com.hello.adapter.border.util.DateTypeConverter.parseDate" printMethod="com.hello.adapter.border.util.DateTypeConverter.printDate" />
  </jaxb:globalBindings>


Version 2.1: Define 2 adapters. DateAdapter, and DateTimeAdapter. Each adapter will extend XMLAdapter and override the marshal/unmarshal methods:

package com.hello.adapter.border.util;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class LocalDateAdapter
    extends XmlAdapter<String, LocalDate>
{

    public LocalDate unmarshal(String inputDate) {
        return inputDate != null ? DateTimeFormatter.ISO_DATE.parse(inputDate, LocalDate::from) : null ;
    }

    public String marshal(LocalDate inputDate) {
        return inputDate != null ? DateTimeFormatter.ISO_DATE.format(inputDate) : null;
    }
}

package com.hello.adapter.border.util;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class LocalDateTimeAdapter
    extends XmlAdapter<String, LocalDateTime>
{

    public LocalDateTime unmarshal(String inputDate) {
        return inputDate != null ? DateTimeFormatter.ofPattern("yyyy-MM-ddTHH.mm.ss.SSS").parse(inputDate, LocalDateTime::from) : null;
    }

    public String marshal(LocalDateTime inputDate) {
        return inputDate != null ? DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(inputDate) : null;
    }

}

Define a bindings.xjb file and <jaxb:globalBindings> section. The global bindings should look like below:

<?xml version="1.0"?>
<jaxb:bindings version="2.1" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
  jaxb:extensionBindingPrefixes="xjc" xmlns:annox="http://annox.dev.java.net" xmlns:inheritance="http://jaxb2-commons.dev.java.net/basic/inheritance" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <jaxb:globalBindings typesafeEnumMemberName="generateName">
      <xjc:javaType name="java.time.LocalDate" xmlType="xs:date" adapter="com.hello.adapter.border.util.LocalDateAdapter" />
       <xjc:javaType name="java.time.LocalDateTime" xmlType="xs:dateTime" adapter="com.hello.adapter.border.util.LocalDateTimeAdapter" />
  </jaxb:globalBindings>

Finally, for either version, we simply need to point towards the bindings.xjb file in our maven-jaxb2-plugin configuration.

  <plugin>
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <version>${maven-jaxb2-plugin.version}</version>
                <executions>
                    <execution>
                        <id>generate-from-xml</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <generateDirectory>target/generated-sources/xjc</generateDirectory>
                            <schemaDirectory>${basedir}/src/main/resources/schemas/xml/xsd</schemaDirectory>
                            <schemaIncludes>
                                <include>**/*.xsd</include>
                            </schemaIncludes>
                            <bindingDirectory>${basedir}/src/main/resources/schemas/xml/xjb</bindingDirectory>
                            <bindingIncludes>
                                <include>**/*.xjb</include>
                            </bindingIncludes>
                            <plugins>
                                <plugin>
                                    <groupId>org.jvnet.jaxb2_commons</groupId>
                                    <artifactId>jaxb2-basics</artifactId>
                                    <version>${jaxb2-basics.version}</version>
                                </plugin>
                                <plugin>
                                    <groupId>org.jvnet.jaxb2_commons</groupId>
                                    <artifactId>jaxb2-basics-annotate</artifactId>
                                    <version>${jaxb2-basics-annotate.version}</version>
                                </plugin>
                            </plugins>
                            <args>
                                <arg>-Xannotate</arg>
                                <arg>-Xinheritance</arg>
                            </args>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

After mvn clean install, all /target/generated/schema classes should contain LocalDate/LocalDateTime attributes, and a usage of the annotation @XmlJavaTypeAdapter

The following xml definition attributes,

<xs:element name="ApprovalDate" type="xs:date" minOccurs="0"/>
<xs:element name="SendTime" type="xs:dateTime">

will generate the following Java attributes:

If using jaxb:bindings version=1

@XmlElement(name = "ApprovalDate", type = String.class)
@XmlJavaTypeAdapter(Adapter2 .class)
@XmlSchemaType(name = "date")
protected LocalDate approvalDate;

@XmlElement(name = "SendTime", required = true, type = String.class)
@XmlJavaTypeAdapter(Adapter1 .class)
@XmlSchemaType(name = "dateTime")
protected LocalDateTime sendTime;

For version=1, we will get autogenerated Adapter1 and Adapter2 which use our final class with methods printDate/parseDate methods

while if using jaxb:bindings version=2.1, then there are no intermediate classes, and JAXB will point to our custom LocalDateAdapter and LocalDateTimeAdapters.

@XmlElement(name = "ApprovalDate", type = String.class)
@XmlJavaTypeAdapter(LocalDateAdapter.class)
@XmlSchemaType(name = "date")
protected LocalDate approvalDate;

@XmlElement(name = "SendTime", required = true, type = String.class)
@XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
@XmlSchemaType(name = "dateTime")
protected LocalDateTime sendTime;

2 thoughts on “Jaxb Bindings for java.time (LocalDate, LocalDateTime)

  1. Thank you very much. This article was really helpful to me.
    Why did you use the Pattern “yyyy-MM-ddTHH.mm.ss.SSS”?

    Why did you this:
    DateTimeFormatter.ofPattern(“yyyy-MM-ddTHH.mm.ss.SSS”).parse(inputDate, LocalDateTime::from)

    Instead of this:
    DateTimeFormatter.ISO_LOCAL_DATE_TIME.parse(inputDate, LocalDateTime::from)

    Like

Leave a comment