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); } } }
AnnotationPurpose
@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
@XmlTransientExcludes a field from XML binding
@XmlType(propOrder={"a","b"})Controls the element order in the output XML
@XmlValueMaps the text content of an element to a field
@XmlElementWrapper(name="...")Wraps a collection in a container element
@XmlJavaTypeAdapterPlug in a custom XmlAdapter for type conversion
@XmlSeeAlsoRegister 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"));