JAXB in Java: Binding Java Objects to XML with Annotations

JAXB (Java Architecture for XML Binding) maps between Java objects and XML automatically. Annotate your classes with @XmlRootElement, @XmlElement, and @XmlAttribute, then use Marshaller to write XML and Unmarshaller to read it. JAXB was part of the JDK up to Java 8 and is now available as Jakarta XML Binding (jakarta.xml.bind) via a Maven dependency.

Share

Adding JAXB: Dependency & Module Info

JAXB was removed from the JDK in Java 11 (JEP 320). Add the Jakarta XML Binding API and an implementation.

XML
<!-- 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.

Annotating Java Classes for XML Binding

JAXB discovers the mapping between your Java fields and XML by reading annotations you place directly on the class. @XmlRootElement marks the class as an XML document root and sets the element tag name. @XmlElement maps a field to a child element, while @XmlAttribute maps it to an XML attribute instead. @XmlAccessorType(XmlAccessType.FIELD) tells JAXB to bind fields directly rather than looking for JavaBean getter/setter pairs. Every JAXB-bound class must have a public no-argument constructor: JAXB needs it to instantiate the object during unmarshalling.

Java
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
}

Marshalling: Java Object to XML

Java
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.

Unmarshalling: XML to Java Object

Java
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);
        }
    }
}

JAXB Annotation Reference

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

XmlAdapter: Custom Type Conversion

Use XmlAdapter<XmlType, BoundType> to marshal/unmarshal types that JAXB cannot handle natively (e.g., LocalDate).

Java
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;
}

Schema Generation & Validation

Java
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"));