1. Uso de adaptadores (@XmlJavaTypeAdapter)
JAXB se parece de verdad a una caja de cambios automática: mientras todo sea estándar — funciona de maravilla, pero en cuanto aparece algo poco habitual, hace falta intervenir manualmente. Imagina que has añadido a tu clase un campo de tipo LocalDate o BigDecimal. JAXB se quedará perplejo — simplemente no sabe cómo convertirlos a XML y viceversa. O quieres que la fecha no se vea como una cadena larga 2024-06-01T00:00:00, sino en un formato habitual 01.06.2024. O quizá tienes un objeto que tiene más sentido guardar en un atributo en lugar de un elemento, o una colección con objetos anidados que necesita una representación especial.
Todas estas situaciones se resuelven con adaptadores. Con ellos puedes indicarle a JAXB exactamente cómo serializar y deserializar campos complejos, definir el formato necesario o incluso omitir datos innecesarios. Es el «cambio manual de marchas» que da flexibilidad cuando el automático ya no basta.
¿Qué es un adaptador?
Un adaptador es una clase especial que le dice a JAXB: «Si te encuentras con este tipo, serialízalo así y deserialízalo asá». En Java un adaptador implementa la clase abstracta javax.xml.bind.annotation.adapters.XmlAdapter<ValueType, BoundType>, donde:
- ValueType — cómo se representarán los datos en XML (normalmente String, a veces Integer, Long, o incluso otro objeto).
- BoundType — el tipo real en tu clase Java (por ejemplo, LocalDate).
Ejemplo: serialización de un campo de tipo LocalDate
import java.time.LocalDate;
import javax.xml.bind.annotation.*;
@XmlRootElement
public class Person {
private String name;
private LocalDate birthDate; // Aquí está el problema
public Person() {} // JAXB exige un constructor público sin parámetros
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; }
}
Si intentas serializar un objeto así, JAXB lanzará una excepción:
javax.xml.bind.JAXBException: class java.time.LocalDate nor any of its super class is known to this context.
Paso 1: crear el adaptador
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
// Adaptador para convertir 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 — convierte el objeto Java (LocalDate) en una cadena para el XML.
- unmarshal — convierte la cadena del XML de vuelta a un objeto Java.
Paso 2: anotar el campo o el getter
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }
O puedes poner la anotación directamente en el campo:
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;
Paso 3: comprobar el resultado
Ahora, al serializar, el objeto tendrá este aspecto:
<Person>
<name>Ivan</name>
<birthDate>01.06.2024</birthDate>
</Person>
Y a la inversa: al leer desde XML, la cadena "01.06.2024" se convertirá en un objeto LocalDate.
2. Aplicar el adaptador al campo, al getter o a toda la clase
Un adaptador puede aplicarse de distintas formas.
A un campo concreto o a su getter: Es el caso más habitual.
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;
A toda la clase: Si quieres que JAXB serialice siempre un tipo mediante un adaptador, puedes anotar la propia clase:
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public class LocalDate { ... }
Normalmente esto se hace con tus propias clases, no con las estándar (la clase LocalDate no se puede modificar).
A colecciones: Puedes serializar, por ejemplo, List<LocalDate> con un adaptador que convierta la lista de fechas en una lista de cadenas.
3. Personalización de nombres de elementos y atributos
A veces los requisitos de la estructura XML son estrictos: por ejemplo, el cliente quiere que el campo no se llame <birthDate>, sino <birth_date>, o que la fecha de nacimiento sea un atributo en lugar de un elemento.
Cambiar el nombre de un elemento
@XmlElement(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }
En el XML ahora será:
<birth_date>01.06.2024</birth_date>
Serializar como atributo
@XmlAttribute(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }
En el XML:
<Person birth_date="01.06.2024">
<name>Ivan</name>
</Person>
Combinación con un adaptador
@XmlAttribute(name = "birth_date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }
4. Casos prácticos
Omitir campos (@XmlTransient)
A veces necesitas que un campo no aparezca en el XML en absoluto (por ejemplo, un identificador interno, una contraseña o datos temporales).
@XmlTransient
private String internalCode;
Ese campo será ignorado durante la serialización y la deserialización.
Formateo de números
Supongamos que tienes un campo con un importe monetario:
private BigDecimal balance;
JAXB no sabe serializar BigDecimal en el formato que necesitas (por ejemplo, con dos decimales y coma decimal). Escribimos un adaptador:
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);
}
}
Y lo usamos:
@XmlJavaTypeAdapter(BigDecimalAdapter.class)
private BigDecimal balance;
Estructuras anidadas
Si tienes objetos anidados, por ejemplo:
public class Address {
private String city;
private String street;
// ...
}
JAXB serializa los objetos anidados como elementos automáticamente. Pero si necesitas que, por ejemplo, city sea un atributo y street — un elemento, usa anotaciones:
public class Address {
@XmlAttribute
private String city;
@XmlElement
private String street;
}
5. Ejemplo: configuración completa de la serialización con adaptador
Vamos a ampliar la aplicación: ahora tenemos una clase Person con fecha de nacimiento y saldo.
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 y setters...
}
Qué hemos conseguido:
- El nombre se serializa como elemento <name>.
- La fecha de nacimiento se serializa como atributo <Person birth_date="01.06.2024">.
- El saldo se serializa como elemento <balance>1234.56</balance>.
- La contraseña no aparece en el XML en absoluto.
Archivo XML:
<Person birth_date="01.06.2024">
<name>Ivan</name>
<balance>1234.56</balance>
</Person>
6. Tratamiento de colecciones y objetos anidados
JAXB sabe trabajar con colecciones si están correctamente anotadas. Por ejemplo, si una persona tiene una lista de direcciones:
@XmlElementWrapper(name = "addresses")
@XmlElement(name = "address")
private List<Address> addresses;
En XML se verá así:
<addresses>
<address city="Berlin">
<street>Alexanderplatz, 1</street>
</address>
<address city="Limassol">
<street>Aneksartisias, 10</street>
</address>
</addresses>
Si el tipo dentro de la colección no es estándar (por ejemplo, List<LocalDate>), puedes aplicar un adaptador al elemento de la colección:
@XmlElementWrapper(name = "dates")
@XmlElement(name = "date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private List<LocalDate> importantDates;
7. Ejemplo: serialización y deserialización con adaptador
Serialización
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); // Imprimirá el XML en la consola
Deserialización
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. Errores típicos al configurar la serialización y los adaptadores
Error n.º 1: falta de constructor público sin parámetros. Si no existe, JAXB no podrá crear el objeto durante la deserialización y lanzará una excepción.
Error n.º 2: aplicación incorrecta del adaptador. Si pones @XmlJavaTypeAdapter en el campo equivocado o te olvidas del getter/setter, JAXB no sabrá cómo serializar el tipo necesario.
Error n.º 3: desajuste de formato en la deserialización. Si en el XML la fecha está en un formato que tu adaptador no soporta (por ejemplo, "2024-06-01" en lugar de "01.06.2024"), el método unmarshal lanzará una excepción.
Error n.º 4: intentar serializar un tipo no soportado por JAXB sin adaptador. Ejemplos típicos — LocalDate, BigDecimal, Map, tipos complejos personalizados.
Error n.º 5: ignorar colecciones anidadas sin anotaciones. Sin @XmlElementWrapper la colección puede no serializarse como esperas o JAXB puede no ser capaz de leer correctamente el XML de vuelta.
Error n.º 6: aplicar el adaptador a la colección en lugar de al elemento. Si quieres serializar los elementos de una lista mediante un adaptador, coloca la anotación en el elemento, no en la colección (por ejemplo, @XmlJavaTypeAdapter sobre el campo del elemento, o sobre el campo de la lista indicando el tipo del elemento, como en los ejemplos anteriores).
GO TO FULL VERSION