1. Introducción a JAXB
JAXB (Java Architecture for XML Binding) — es una tecnología estándar de Java para transformar (binding) objetos Java en XML y viceversa. Con JAXB se pueden serializar fácilmente objetos a archivos XML y luego reconstruirlos a partir de esos archivos.
JAXB formaba parte de la biblioteca estándar de Java hasta la versión 11 inclusive. A partir de Java 11, JAXB se separó en un módulo independiente que hay que añadir mediante Maven/Gradle o descargar manualmente. Para versiones modernas de Java, añade las dependencias:
<!-- Ejemplo para Maven -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.3</version>
</dependency>
¿Para qué sirve XML?
- XML es un formato universal y legible por humanos, ampliamente utilizado para el intercambio de datos entre sistemas, la configuración y el almacenamiento de información.
- A diferencia de la serialización binaria, el XML se puede leer a simple vista, validar contra un esquema y abrir en el navegador.
2. Clases y anotaciones principales de JAXB
JAXB funciona sobre la base de anotaciones con las que se marcan las clases y sus campos para controlar el proceso de serialización/deserialización.
Anotaciones clave
| Anotación | Para qué sirve |
|---|---|
|
Indica el elemento raíz del XML (la clase en sí) |
|
Marca un campo/propiedad como elemento XML |
|
Marca un campo/propiedad como atributo XML |
|
Controla el orden de los elementos, el nombre del tipo, etc. |
|
Excluye el campo de la serialización |
Clases principales
- JAXBContext — punto de entrada; crea un contexto para la serialización/deserialización de clases concretas.
- Marshaller — convierte un objeto a XML (marshalling, marshal()).
- Unmarshaller — convierte XML a un objeto (unmarshalling, unmarshal()).
3. Ejemplo: serialización de un objeto a XML
Creemos una clase que vamos a serializar. Que sea un personaje para nuestro juego:
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlAttribute;
@XmlRootElement(name = "player")
public class Player {
private String name;
private int level;
private int health;
public Player() {} // ¡Constructor vacío obligatorio!
public Player(String name, int level, int health) {
this.name = name;
this.level = level;
this.health = health;
}
@XmlElement
public String getName() {
return name;
}
public void setName(String name) { this.name = name; }
@XmlElement
public int getLevel() {
return level;
}
public void setLevel(int level) { this.level = level; }
@XmlAttribute
public int getHealth() {
return health;
}
public void setHealth(int health) { this.health = health; }
}
- @XmlRootElement(name = "player") — la clase se convierte en el elemento raíz <player>.
- @XmlElement — el campo será un elemento XML independiente (<name>, <level>).
- @XmlAttribute — el campo será un atributo del elemento raíz (health="100").
- No olvides el constructor sin argumentos. JAXB lo requiere para la deserialización.
Serialización del objeto a XML
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Marshaller;
public class Main {
public static void main(String[] args) throws Exception {
Player player = new Player("Aragorn", 5, 100);
JAXBContext context = JAXBContext.newInstance(Player.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); // Salida formateada
marshaller.marshal(player, System.out); // Escribimos el XML en la consola
// marshaller.marshal(player, new File("player.xml")); // O en un archivo
}
}
Resultado:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<player health="100">
<name>Aragorn</name>
<level>5</level>
</player>
Deserialización de un objeto desde XML
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Unmarshaller;
import java.io.File;
public class Main {
public static void main(String[] args) throws Exception {
JAXBContext context = JAXBContext.newInstance(Player.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Player player = (Player) unmarshaller.unmarshal(new File("player.xml"));
System.out.println(player.getName() + ", nivel: " + player.getLevel() + ", salud: " + player.getHealth());
}
}
4. Características y limitaciones de JAXB
Requisitos para las clases
- Constructor público sin parámetros — obligatorio.
- Para un funcionamiento correcto, utiliza getters y setters.
- Todos los campos serializables deben ser accesibles (a través de la API pública).
- Los objetos anidados y las colecciones también deben ser serializables (anótalos y añade un constructor vacío).
Trabajo con colecciones y objetos anidados
Supongamos que el jugador tiene un inventario (lista de objetos). ¿Cómo serializar una colección?
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import java.util.List;
@XmlRootElement(name = "player")
public class Player {
// ... otros campos
private List<String> inventory;
public Player() {}
// ... demás getters/setters
@XmlElementWrapper(name = "inventory")
@XmlElement(name = "item")
public List<String> getInventory() {
return inventory;
}
public void setInventory(List<String> inventory) {
this.inventory = inventory;
}
}
Resultado de la serialización:
<player health="100">
<name>Aragorn</name>
<level>5</level>
<inventory>
<item>Sword</item>
<item>Shield</item>
</inventory>
</player>
- @XmlElementWrapper — crea una «envoltura» alrededor de la colección (elemento <inventory>).
- @XmlElement(name = "item") — cada elemento de la lista se serializa como <item>.
Si hay objetos anidados (por ejemplo, Position), también deben anotarse y añadir un constructor vacío.
5. Práctica: serializar y deserializar un objeto en XML
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlAttribute;
import java.util.List;
@XmlRootElement(name = "player")
public class Player {
private String name;
private int level;
private int health;
private List<String> inventory;
private Position position;
public Player() {}
public Player(String name, int level, int health, List<String> inventory, Position position) {
this.name = name;
this.level = level;
this.health = health;
this.inventory = inventory;
this.position = position;
}
@XmlElement
public String getName() { return name; }
@XmlElement
public int getLevel() { return level; }
@XmlAttribute
public int getHealth() { return health; }
@XmlElementWrapper(name = "inventory")
@XmlElement(name = "item")
public List<String> getInventory() { return inventory; }
@XmlElement
public Position getPosition() { return position; }
// setters omitidos por brevedad
}
@XmlRootElement(name = "position")
class Position {
private int x;
private int y;
public Position() {}
public Position(int x, int y) { this.x = x; this.y = y; }
@XmlAttribute
public int getX() { return x; }
@XmlAttribute
public int getY() { return y; }
// setters omitidos
}
Serialización:
Player player = new Player(
"Aragorn",
5,
100,
List.of("Sword", "Shield", "Potion"),
new Position(10, 20)
);
JAXBContext context = JAXBContext.newInstance(Player.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(player, System.out);
Resultado en XML:
<player health="100">
<name>Aragorn</name>
<level>5</level>
<inventory>
<item>Sword</item>
<item>Shield</item>
<item>Potion</item>
</inventory>
<position x="10" y="20"/>
</player>
La deserialización funciona de forma análoga: JAXB analizará los objetos anidados y las colecciones si las clases están descritas correctamente.
6. Tabla: anotaciones principales de JAXB y su efecto
| Anotación | Dónde usar | Qué hace en XML |
|---|---|---|
|
Clase | Elemento raíz |
|
Getter/campo | Elemento dentro de XML |
|
Getter/campo | Atributo del elemento |
|
Getter de colección | «Envoltura» de la colección (por ejemplo, <list>) |
|
Campo/getter | Excluye el campo de la serialización |
|
Clase | Controla el orden de los elementos y el nombre del tipo |
7. Características y limitaciones de JAXB
Orden de los elementos
De forma predeterminada, JAXB puede emitir los elementos en orden alfabético. Para especificar el orden explícitamente, utiliza @XmlType y la propiedad propOrder:
@XmlType(propOrder = {"name", "level", "inventory", "position"})
Exclusión de campos
Para no serializar un campo/getter, usa @XmlTransient:
@XmlTransient
public String getSecretCode() { ... }
Problemas con colecciones
- No utilices colecciones «raw» sin genéricos: escribe List<Type>, no List.
- Si la colección almacena objetos, sus clases también deben estar anotadas y tener un constructor vacío.
Errores
- Falta el constructor vacío — obtendrás JAXBException al hacer unmarshalling.
- Clase anidada no anotada — JAXB no podrá serializar/deserializarla.
- Tipos no estándar (por ejemplo, LocalDate) requieren un adaptador (@XmlJavaTypeAdapter).
8. Errores comunes al trabajar con JAXB
Error n.º 1: Falta el constructor sin argumentos. JAXB exige que la clase serializable tenga un constructor público sin parámetros. Si no lo hay, durante el unmarshalling se lanzará la excepción JAXBException.
Error n.º 2: Objetos anidados sin anotar. Si tienes un campo-objeto pero su clase no está anotada con @XmlRootElement o al menos @XmlType, JAXB no podrá serializar/deserializarlo correctamente.
Error n.º 3: Problemas con colecciones. JAXB no entiende las colecciones «raw» sin indicar el tipo de sus elementos. Usa genéricos y anota correctamente las colecciones (@XmlElementWrapper + @XmlElement).
Error n.º 4: Gestión implícita del orden de los elementos. Si el orden de los elementos en el XML es importante para la integración, utiliza @XmlType con propOrder; de lo contrario, JAXB puede emitir los elementos en otro orden (por ejemplo, alfabético).
Error n.º 5: Uso de tipos no estándar sin adaptador. JAXB no sabe serializar algunos tipos (por ejemplo, LocalDate) sin un adaptador. Aplica @XmlJavaTypeAdapter o serializa el valor como cadena.
GO TO FULL VERSION