JAXB was removed from the JDK in Java 11 (JEP 320). Add the Jakarta XML Binding API and an implementation.
<!-- Jakarta XML Binding API -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
</dependency>
<!-- Reference implementation (GlassFish JAXB) -->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.5</version>
<scope>runtime</scope>
</dependency>
Use the jakarta.xml.bind namespace (not the legacy javax.xml.bind) for all new projects. The annotation class names are identical — only the package prefix changes.
import jakarta.xml.bind.annotation.*;
import java.util.List;
@XmlRootElement(name = "library") // maps to <library> root element
@XmlAccessorType(XmlAccessType.FIELD) // bind fields directly (not getters)
public class Library {
@XmlElement(name = "book") // maps to <book> child elements
private List<Book> books;
public Library() {} // JAXB requires a no-arg constructor
public Library(List<Book> books) { this.books = books; }
public List<Book> getBooks() { return books; }
}
@XmlAccessorType(XmlAccessType.FIELD)
public class Book {
@XmlAttribute // maps to id="..." attribute
private int id;
@XmlAttribute(name = "genre")
private String genre;
@XmlElement // maps to <title> child element
private String title;
@XmlElement
private String author;
@XmlElement
private int year;
public Book() {}
public Book(int id, String genre, String title, String author, int year) {
this.id = id; this.genre = genre; this.title = title;
this.author = author; this.year = year;
}
// getters omitted for brevity
}
import jakarta.xml.bind.*;
import java.io.*;
import java.util.List;
public class JaxbMarshalExample {
public static void main(String[] args) throws JAXBException {
Library library = new Library(List.of(
new Book(1, "technical", "Effective Java", "Joshua Bloch", 2018),
new Book(2, "fiction", "Clean Code", "Robert C. Martin", 2008)
));
// Create JAXBContext (expensive — cache as a static field in production)
JAXBContext ctx = JAXBContext.newInstance(Library.class);
// Marshal to System.out with indentation
Marshaller marshaller = ctx.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
marshaller.marshal(library, System.out);
// Marshal to a String
StringWriter sw = new StringWriter();
marshaller.marshal(library, sw);
String xml = sw.toString();
// Marshal to a file
marshaller.marshal(library, new File("library.xml"));
}
}
JAXBContext.newInstance() is thread-safe and expensive to create. Cache it as a static final field. Marshaller and Unmarshaller are not thread-safe — create one per operation or synchronise access.
import jakarta.xml.bind.*;
import java.io.*;
public class JaxbUnmarshalExample {
public static void main(String[] args) throws JAXBException, IOException {
JAXBContext ctx = JAXBContext.newInstance(Library.class);
Unmarshaller unmarshaller = ctx.createUnmarshaller();
// Unmarshal from a file
Library library = (Library) unmarshaller.unmarshal(new File("library.xml"));
library.getBooks().forEach(b ->
System.out.printf("Book %d: %s%n", b.getId(), b.getTitle()));
// Unmarshal from a String
String xml = "<library>...</library>";
Library lib2 = (Library) unmarshaller.unmarshal(
new StringReader(xml));
// Unmarshal from an InputStream
try (InputStream is = new FileInputStream("library.xml")) {
Library lib3 = (Library) unmarshaller.unmarshal(is);
}
}
}
| Annotation | Purpose |
@XmlRootElement(name="...") | Marks the class as an XML root element; name sets element tag |
@XmlElement(name="...", required=true) | Maps a field/property to a child XML element |
@XmlAttribute(name="...") | Maps a field/property to an XML attribute |
@XmlAccessorType(XmlAccessType.FIELD) | Controls which members are bound: FIELD, PROPERTY, PUBLIC_MEMBER, NONE |
@XmlTransient | Excludes a field from XML binding |
@XmlType(propOrder={"a","b"}) | Controls the element order in the output XML |
@XmlValue | Maps the text content of an element to a field |
@XmlElementWrapper(name="...") | Wraps a collection in a container element |
@XmlJavaTypeAdapter | Plug in a custom XmlAdapter for type conversion |
@XmlSeeAlso | Register sub-types for polymorphic binding |
Use XmlAdapter<XmlType, BoundType> to marshal/unmarshal types that JAXB cannot handle natively (e.g., LocalDate).
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDate;
public class LocalDateAdapter extends XmlAdapter<String, LocalDate> {
@Override
public LocalDate unmarshal(String v) {
return v != null ? LocalDate.parse(v) : null;
}
@Override
public String marshal(LocalDate v) {
return v != null ? v.toString() : null;
}
}
// Apply on the field
public class Book {
@XmlJavaTypeAdapter(LocalDateAdapter.class)
@XmlElement
private LocalDate publishedDate;
}
import jakarta.xml.bind.*;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.XMLConstants;
import java.io.File;
// Generate XSD from annotated classes
JAXBContext ctx = JAXBContext.newInstance(Library.class);
ctx.generateSchema(new SchemaOutputResolver() {
@Override
public javax.xml.transform.Result createOutput(String namespaceUri, String suggestedFileName)
throws java.io.IOException {
return new javax.xml.transform.stream.StreamResult(new File(suggestedFileName));
}
});
// Creates library.xsd in current directory
// Validate XML against XSD during unmarshal
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new File("library.xsd"));
Unmarshaller u = ctx.createUnmarshaller();
u.setSchema(schema); // validation happens automatically during unmarshal
u.setEventHandler(event -> {
System.err.println("Validation: " + event.getMessage());
return true; // true = continue on validation errors; false = abort
});
Library lib = (Library) u.unmarshal(new File("library.xml"));