1. Using adapters (@XmlJavaTypeAdapter)
JAXB is really like an automatic transmission: as long as everything is standard it works perfectly, but once you run into something unusual, you can’t do without manual intervention. Imagine you add a field of type LocalDate or BigDecimal to your class. JAXB will get confused — it simply doesn’t know how to turn them into XML and back. Or you want the date not to look like a long string 2024-06-01T00:00:00, but in the familiar format 01.06.2024. Or perhaps you have an object that makes more sense to store in an attribute rather than an element, or a collection with nested objects that requires a special representation.
All these situations are solved with adapters. With them, you can tell JAXB exactly how to serialize and deserialize complex fields, set the desired format, or even skip unnecessary data. This is the “manual gear control” that gives you flexibility where the automatic no longer copes.
What is an adapter?
An adapter is a special class that tells JAXB: “If you encounter this type, serialize it like this, and deserialize it like that.” In Java, an adapter implements the abstract class javax.xml.bind.annotation.adapters.XmlAdapter<ValueType, BoundType>, where:
- ValueType — how the data will be represented in XML (usually a String, sometimes an Integer, Long, or even another object).
- BoundType — the actual type in your Java class (for example, LocalDate).
Example: serializing a LocalDate field
import java.time.LocalDate;
import javax.xml.bind.annotation.*;
@XmlRootElement
public class Person {
private String name;
private LocalDate birthDate; // Here's the problem!
public Person() {} // JAXB requires a public no-arg constructor
public Person(String name, LocalDate birthDate) {
this.name = name;
this.birthDate = birthDate;
}
@XmlElement
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@XmlElement
public LocalDate getBirthDate() { return birthDate; }
public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
}
If you try to serialize such an object, JAXB will throw an exception:
javax.xml.bind.JAXBException: class java.time.LocalDate nor any of its super class is known to this context.
Step 1: Create an adapter
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
// Adapter to convert LocalDate <-> String
public class LocalDateAdapter extends XmlAdapter<String, LocalDate> {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy");
@Override
public LocalDate unmarshal(String v) throws Exception {
return (v == null || v.isEmpty()) ? null : LocalDate.parse(v, FORMATTER);
}
@Override
public String marshal(LocalDate v) throws Exception {
return (v == null) ? null : v.format(FORMATTER);
}
}
- marshal — converts a Java object (LocalDate) into a string for XML.
- unmarshal — converts a string from XML back into a Java object.
Step 2: Annotate the field or getter
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }
Or annotate the field itself:
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;
Step 3: Check the result
Now, when serializing, the object will look like this:
<Person>
<name>Ivan</name>
<birthDate>01.06.2024</birthDate>
</Person>
And conversely — when reading from XML, the string "01.06.2024" will become a LocalDate object.
2. Applying an adapter to a field, getter, or the whole class
An adapter can be applied in different ways.
To an individual field or getter: This is the most common case.
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;
To an entire class: If you want JAXB to always serialize a given type via an adapter, you can annotate the class itself:
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public class LocalDate { ... }
Usually, this is done for your own classes, not standard ones (you cannot modify LocalDate).
For collections: You can serialize, for example, a List<LocalDate> via an adapter that turns a list of dates into a list of strings.
3. Customizing element and attribute names
Sometimes XML structure requirements are strict: for example, a client wants the field to be named not <birthDate> but <birth_date>, or wants the birth date as an attribute rather than an element.
Changing the element name
@XmlElement(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }
In XML it will now be:
<birth_date>01.06.2024</birth_date>
Serializing as an attribute
@XmlAttribute(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }
In XML:
<Person birth_date="01.06.2024">
<name>Ivan</name>
</Person>
Combining with an adapter
@XmlAttribute(name = "birth_date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }
4. Practical cases
Skipping fields (@XmlTransient)
Sometimes you need a field not to appear in XML at all (for example, an internal ID, password, or temporary data).
@XmlTransient
private String internalCode;
Such a field will be ignored during both serialization and deserialization.
Number formatting
Suppose you have a field with a monetary amount:
private BigDecimal balance;
JAXB won’t serialize BigDecimal in the format you want by itself (for example, with two fraction digits, or handling a comma decimal separator). Let’s write an adapter:
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.math.BigDecimal;
public class BigDecimalAdapter extends XmlAdapter<String, BigDecimal> {
@Override
public BigDecimal unmarshal(String v) throws Exception {
return (v == null || v.isEmpty()) ? null : new BigDecimal(v.replace(",", "."));
}
@Override
public String marshal(BigDecimal v) throws Exception {
return (v == null) ? null : String.format("%.2f", v);
}
}
And use it:
@XmlJavaTypeAdapter(BigDecimalAdapter.class)
private BigDecimal balance;
Nested structures
If you have nested objects, for example:
public class Address {
private String city;
private String street;
// ...
}
JAXB will serialize nested objects as elements by default. But if you need, for example, city as an attribute and street as an element, use annotations:
public class Address {
@XmlAttribute
private String city;
@XmlElement
private String street;
}
5. Example: full serialization customization with an adapter
Let’s evolve the app: now we have a Person class with a birth date and a balance.
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.math.BigDecimal;
import java.time.LocalDate;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {
@XmlElement
private String name;
@XmlAttribute(name = "birth_date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;
@XmlElement
@XmlJavaTypeAdapter(BigDecimalAdapter.class)
private BigDecimal balance;
@XmlTransient
private String password;
public Person() {}
public Person(String name, LocalDate birthDate, BigDecimal balance, String password) {
this.name = name;
this.birthDate = birthDate;
this.balance = balance;
this.password = password;
}
// getters and setters...
}
What we get:
- Name is serialized as an element <name>.
- Birth date is serialized as an attribute <Person birth_date="01.06.2024">.
- Balance is serialized as an element <balance>1234.56</balance>.
- Password does not appear in XML at all.
XML file:
<Person birth_date="01.06.2024">
<name>Ivan</name>
<balance>1234.56</balance>
</Person>
6. Handling collections and nested objects
JAXB can work with collections if they are annotated properly. For example, if a person has a list of addresses:
@XmlElementWrapper(name = "addresses")
@XmlElement(name = "address")
private List<Address> addresses;
In XML this will look like:
<addresses>
<address city="Berlin">
<street>Alexanderplatz, 1</street>
</address>
<address city="Limassol">
<street>Anexartisias, 10</street>
</address>
</addresses>
If the type in the collection is non-standard (for example, List<LocalDate>), you can apply an adapter to the collection element:
@XmlElementWrapper(name = "dates")
@XmlElement(name = "date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private List<LocalDate> importantDates;
7. Example: serialization and deserialization with an adapter
Serialization
Person person = new Person(
"Ivan",
LocalDate.of(1990, 6, 1),
new BigDecimal("1234.56"),
"secretPassword"
);
JAXBContext context = JAXBContext.newInstance(Person.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(person, System.out); // Will print XML to the console
Deserialization
String xml = """
<Person birth_date="01.06.1990">
<name>Ivan</name>
<balance>1234.56</balance>
</Person>
""";
JAXBContext context = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Person person = (Person) unmarshaller.unmarshal(new StringReader(xml));
System.out.println(person.getName() + " " + person.getBirthDate() + " " + person.getBalance());
8. Common mistakes when configuring serialization and adapters
Error #1: Missing public no-arg constructor. Without it, JAXB cannot create an object during deserialization and will throw an exception.
Error #2: Incorrect adapter application. If you put @XmlJavaTypeAdapter on the wrong field or forget a getter/setter, JAXB won’t know how to serialize the desired type.
Error #3: Format mismatch during deserialization. If the date in XML is in a format your adapter does not support (for example, "2024-06-01" instead of "01.06.2024"), the unmarshal method will throw an exception.
Error #4: Trying to serialize a type that JAXB does not support without an adapter. Typical examples are LocalDate, BigDecimal, Map, and custom complex types.
Error #5: Ignoring nested collections without annotations. Without @XmlElementWrapper, a collection may not serialize as expected, or JAXB might not be able to correctly read the XML back.
Error #6: Applying an adapter to the collection itself rather than its elements. If you want to serialize list elements via an adapter, put the annotation on the element, not on the collection itself (for example, @XmlJavaTypeAdapter on the element field, or on the list field with the element type specified, as in the examples above).
GO TO FULL VERSION