JAXB XmlAdapter for Non-Standard Data Types
The Java Architecture for XML Binding (JAXB) framework uses the
XmlAdapter class to map non-standard, unannotated, or
complex Java data types to easily serializable XML formats. By defining
a two-way translation layer between an unmappable Java type and a
JAXB-friendly representation, XmlAdapter allows developers
to serialize objects such as modern date-time classes, interfaces,
third-party library classes, and custom map structures without altering
the underlying domain models.
The Role of XmlAdapter
By default, JAXB can only marshal and unmarshal types that adhere to
the JavaBean convention, primitive types, standard collections, or
classes decorated with JAXB annotations. When encountering unsupported
types—such as java.time.LocalDate, abstract types, or
third-party objects that cannot be directly annotated—JAXB throws
runtime exceptions during serialization.
The abstract class
jakarta.xml.bind.annotation.adapters.XmlAdapter<ValueType, BoundType>
resolves this by acting as an intermediary. It uses two generic
parameters:
BoundType: The custom or non-standard Java type used in your application domain.ValueType: The standard, schema-compatible type that JAXB natively knows how to marshal and unmarshal (such asString,Integer, or a custom JAXB-annotated wrapper class).
Key Methods
To create a custom adapter, you extend XmlAdapter and
implement two essential methods:
marshal(BoundType v): Invoked during serialization (Java to XML). It converts the complexBoundTypeinto the simplerValueType.unmarshal(ValueType v): Invoked during deserialization (XML to Java). It converts the simpleValueTypeback into the targetBoundType.
Implementation Example
To serialize a java.time.LocalDate object into an
ISO-8601 string format, create a custom adapter:
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class LocalDateAdapter extends XmlAdapter<String, LocalDate> {
@Override
public String marshal(LocalDate date) throws Exception {
return date != null ? date.format(DateTimeFormatter.ISO_LOCAL_DATE) : null;
}
@Override
public LocalDate unmarshal(String dateString) throws Exception {
return dateString != null ? LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE) : null;
}
}Applying the Adapter
Once implemented, the adapter must be registered using the
@XmlJavaTypeAdapter annotation. JAXB supports application
at multiple levels:
Field or Property Level: Applied directly over a specific field or getter/setter method.
@XmlElement @XmlJavaTypeAdapter(LocalDateAdapter.class) private LocalDate creationDate;Type Level: Applied over a class definition to make the adapter active whenever that class is used.
@XmlJavaTypeAdapter(LocalDateAdapter.class) public class CustomDateType { ... }Package Level: Applied in a
package-info.javafile to automatically handle all instances of the data type within that entire package without manual field annotations.@XmlJavaTypeAdapters({ @XmlJavaTypeAdapter(value = LocalDateAdapter.class, type = LocalDate.class) }) package com.example.model; import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapters; import java.time.LocalDate;
Handling Complex Structures
For structures like java.util.Map, which JAXB cannot
handle out of the box, ValueType can be a custom
JAXB-annotated class containing a list of key-value entries. The adapter
then converts the Map into a list of entry objects during
marshalling and reconstructs the Map during unmarshalling,
ensuring clean and compliant XML generation.