1. Verwendung von Adaptern (@XmlJavaTypeAdapter)
JAXB ist tatsächlich wie ein Automatikgetriebe: Solange alles standardmäßig ist – funktioniert es perfekt, doch sobald etwas Ungewöhnliches auftaucht, kommt man ohne manuelles Eingreifen nicht aus. Stellen Sie sich vor, Sie fügen Ihrer Klasse ein Feld vom Typ LocalDate oder BigDecimal hinzu. JAXB ist dann überfordert – es weiß schlicht nicht, wie es diese Typen in XML und zurück verwandeln soll. Oder Sie möchten, dass ein Datum nicht als lange Zeichenkette wie 2024-06-01T00:00:00 erscheint, sondern im gewohnten Format 01.06.2024. Vielleicht gibt es auch ein Objekt, das logisch eher als Attribut statt als Element gespeichert werden soll, oder eine Collection mit verschachtelten Objekten, die eine besondere Darstellung erfordert.
All diese Situationen lassen sich mit Adaptern lösen. Damit können Sie JAXB genau vorgeben, wie komplexe Felder serialisiert und deserialisiert werden sollen, das gewünschte Format definieren oder sogar unnötige Daten auslassen. Das ist das „manuelle Schalten“, das dort Flexibilität bietet, wo die Automatik nicht mehr ausreicht.
Was ist ein Adapter?
Ein Adapter ist eine spezielle Klasse, die JAXB sagt: „Wenn du auf diesen Typ triffst, serialisiere ihn so und deserialisiere ihn so.“ In Java implementiert ein Adapter die abstrakte Klasse javax.xml.bind.annotation.adapters.XmlAdapter<ValueType, BoundType>, wobei:
- ValueType – wie die Daten in XML dargestellt werden (meist String, manchmal Integer, Long oder sogar ein anderes Objekt).
- BoundType – der tatsächliche Typ in Ihrer Java-Klasse (zum Beispiel LocalDate).
Beispiel: Serialisierung eines Feldes vom Typ LocalDate
import java.time.LocalDate;
import javax.xml.bind.annotation.*;
@XmlRootElement
public class Person {
private String name;
private LocalDate birthDate; // Hier liegt das Problem!
public Person() {} // JAXB benötigt einen öffentlichen, parameterlosen Konstruktor
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; }
}
Wenn man versucht, ein solches Objekt zu serialisieren, wirft JAXB eine Exception:
javax.xml.bind.JAXBException: class java.time.LocalDate nor any of its super class is known to this context.
Schritt 1: Adapter erstellen
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
// Adapter für die Umwandlung 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 – wandelt ein Java-Objekt (LocalDate) in eine Zeichenkette für XML um.
- unmarshal – wandelt eine Zeichenkette aus XML zurück in ein Java-Objekt.
Schritt 2: Feld oder Getter mit der Annotation versehen
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }
Oder die Annotation direkt am Feld anbringen:
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;
Schritt 3: Ergebnis prüfen
Bei der Serialisierung sieht das Objekt nun so aus:
<Person>
<name>Ivan</name>
<birthDate>01.06.2024</birthDate>
</Person>
Und umgekehrt – beim Einlesen aus XML wird die Zeichenkette "01.06.2024" in ein LocalDate-Objekt umgewandelt.
2. Adapter auf Feld, Getter oder die ganze Klasse anwenden
Einen Adapter kann man auf verschiedene Weise anwenden.
Auf ein einzelnes Feld oder einen Getter: Das ist der häufigste Fall.
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;
Auf die gesamte Klasse: Wenn JAXB einen Typ immer über einen Adapter serialisieren soll, kann man die Klasse selbst annotieren:
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public class LocalDate { ... }
In der Regel macht man das bei eigenen Klassen, nicht bei Standardklassen (die Klasse LocalDate kann man nicht modifizieren).
Auf Collections: Man kann z. B. List<LocalDate> über einen Adapter serialisieren, der eine Liste von Daten in eine Liste von Strings verwandelt.
3. Anpassung von Element- und Attributnamen
Manchmal sind die Anforderungen an die XML-Struktur strikt: Der Auftraggeber möchte etwa, dass das Feld nicht <birthDate>, sondern <birth_date> heißt, oder dass das Geburtsdatum ein Attribut statt eines Elements ist.
Elementnamen ändern
@XmlElement(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }
Im XML steht dann:
<birth_date>01.06.2024</birth_date>
Als Attribut serialisieren
@XmlAttribute(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }
Im XML:
<Person birth_date="01.06.2024">
<name>Ivan</name>
</Person>
Kombination mit Adapter
@XmlAttribute(name = "birth_date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }
4. Praktische Anwendungsfälle
Felder auslassen (@XmlTransient)
Manchmal soll ein Feld überhaupt nicht in XML erscheinen (z. B. interne Kennung, Passwort, temporäre Daten).
@XmlTransient
private String internalCode;
Ein solches Feld wird bei Serialisierung und Deserialisierung ignoriert.
Zahlen formatieren
Angenommen, Sie haben ein Feld mit einem Geldbetrag:
private BigDecimal balance;
JAXB kann BigDecimal nicht in Ihrem gewünschten Format serialisieren (z. B. mit zwei Nachkommastellen, mit Komma). Wir schreiben einen 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);
}
}
Und verwenden ihn so:
@XmlJavaTypeAdapter(BigDecimalAdapter.class)
private BigDecimal balance;
Verschachtelte Strukturen
Wenn Sie verschachtelte Objekte haben, zum Beispiel:
public class Address {
private String city;
private String street;
// ...
}
JAXB serialisiert verschachtelte Objekte automatisch als Elemente. Wenn jedoch zum Beispiel city ein Attribut und street ein Element sein soll, nutzen Sie Annotationen:
public class Address {
@XmlAttribute
private String city;
@XmlElement
private String street;
}
5. Beispiel: Vollständige Serialisierungskonfiguration mit Adapter
Wir entwickeln die Anwendung weiter: Jetzt gibt es eine Klasse Person mit Geburtsdatum und Kontostand.
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;
}
// Getter und Setter ...
}
Ergebnis:
- Der Name wird als Element <name> serialisiert.
- Das Geburtsdatum wird als Attribut serialisiert: <Person birth_date="01.06.2024">.
- Der Kontostand wird als Element <balance>1234.56</balance> serialisiert.
- Das Passwort erscheint gar nicht im XML.
XML-Datei:
<Person birth_date="01.06.2024">
<name>Ivan</name>
<balance>1234.56</balance>
</Person>
6. Verarbeitung von Collections und verschachtelten Objekten
JAXB kann mit Collections umgehen, wenn sie korrekt annotiert sind. Wenn eine Person zum Beispiel eine Adressliste hat:
@XmlElementWrapper(name = "addresses")
@XmlElement(name = "address")
private List<Address> addresses;
Im XML sieht das so aus:
<addresses>
<address city="Berlin">
<street>Alexanderplatz, 1</street>
</address>
<address city="Limassol">
<street>Anexartisias, 10</street>
</address>
</addresses>
Wenn der Typ in der Collection nicht standardmäßig ist (z. B. List<LocalDate>), kann man einen Adapter auf das Collection-Element anwenden:
@XmlElementWrapper(name = "dates")
@XmlElement(name = "date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private List<LocalDate> importantDates;
7. Beispiel: Serialisierung und Deserialisierung mit Adapter
Serialisierung
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); // Gibt XML auf der Konsole aus
Deserialisierung
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. Typische Fehler bei der Konfiguration von Serialisierung und Adaptern
Fehler Nr. 1: Kein öffentlicher, parameterloser Konstruktor. Ohne ihn kann JAXB beim Deserialisieren kein Objekt erzeugen und wirft eine Exception.
Fehler Nr. 2: Falsche Anwendung des Adapters. Wenn @XmlJavaTypeAdapter am falschen Feld sitzt oder Getter/Setter fehlen, weiß JAXB nicht, wie der gewünschte Typ serialisiert werden soll.
Fehler Nr. 3: Formatabweichung bei der Deserialisierung. Wenn das Datum im XML in einem von Ihrem Adapter nicht unterstützten Format steht (z. B. "2024-06-01" statt "01.06.2024"), wirft die Methode unmarshal eine Exception.
Fehler Nr. 4: Versuch, einen von JAXB nicht unterstützten Typ ohne Adapter zu serialisieren. Typische Beispiele sind LocalDate, BigDecimal, Map, benutzerdefinierte komplexe Typen.
Fehler Nr. 5: Verschachtelte Collections ohne Annotationen ignorieren. Ohne @XmlElementWrapper wird eine Collection nicht wie erwartet serialisiert, oder JAXB kann das XML beim Einlesen nicht korrekt verarbeiten.
Fehler Nr. 6: Adapter auf die Collection statt auf das Element anwenden. Wenn die Elemente einer Liste über einen Adapter serialisiert werden sollen, setzen Sie die Annotation am Element und nicht an der Collection selbst (z. B. @XmlJavaTypeAdapter am Elementfeld oder am Listenfeld mit Angabe des Elementtyps, wie oben gezeigt).
GO TO FULL VERSION