JAXB Unmarshaller Example: Convert XML to Java Object

Convert XML to Java objects with the Jakarta XML Binding Unmarshaller. Read XML from a File, String, InputStream or URL. Use JAXBElement for classes without @XmlRootElement, validate against an XSD, map namespaces and move from javax to jakarta.

Java 10

A JAXB Unmarshaller reads XML and turns it into a Java object. The annotations on our class, such as @XmlRootElement, tell JAXB how the XML elements map to the fields.

We unmarshal XML whenever an app reads XML input into Java objects, such as a product feed from a partner or a SOAP response.

Since Java 11, the JDK no longer ships JAXB, so we add two Jakarta XML Binding 4 dependencies. Our code compiles against the API jakarta.xml.bind-api, and jaxb-runtime is the implementation that reads the XML.

<dependency>
  <groupId>jakarta.xml.bind</groupId>
  <artifactId>jakarta.xml.bind-api</artifactId>
  <version>4.0.5</version>
</dependency>
<dependency>
  <groupId>org.glassfish.jaxb</groupId>
  <artifactId>jaxb-runtime</artifactId>
  <version>4.0.9</version>
  <scope>runtime</scope>
</dependency>

The following example reads song.xml into a Song object. The call JAXBContext.newInstance() creates the context, and createUnmarshaller() creates the unmarshaller from it. Then unmarshal() reads the XML and returns the object.

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Unmarshaller;

JAXBContext context = JAXBContext.newInstance(Song.class);
Unmarshaller unmarshaller = context.createUnmarshaller();

Song song = (Song) unmarshaller.unmarshal(new File("song.xml"));
// Song[id=7, title=Imagine, artist=John Lennon, seconds=183]

Notice that unmarshal() returns an Object, so we cast the result to Song.

Next, we use the same call for a String, an InputStream, a URL and a DOM node. Then we read XML without @XmlRootElement, validate it against an XSD schema and handle namespaces, and we finish with the move from javax.xml.bind to jakarta.xml.bind and the most common errors.

1. What Is a JAXB Unmarshaller?

Unmarshalling means turning XML text into Java objects, whereas the opposite direction, from Java objects to XML, is marshalling with the JAXB Marshaller. Two types from the jakarta.xml.bind package do the unmarshalling work.

TypeWhat it doesCreate itShare between threads?
JAXBContextReads the annotations of our classes once and keeps the XML-to-Java mappingJAXBContext.newInstance(Song.class)Yes. Create it once and reuse it.
UnmarshallerReads one XML input and builds the Java objectscontext.createUnmarshaller()No. Create one per thread or per call.

Creating a JAXBContext is slow because JAXB inspects every annotation of the classes at that moment, whereas creating an Unmarshaller from an existing context is cheap. So we keep one JAXBContext per set of classes, for example in a static field, and create a new Unmarshaller wherever we need one.

One unmarshal() call works in four steps. It parses the XML and, if we set a schema, validates the XML against it. Then it finds the class for the root element (that is, the outermost XML element), creates the object and fills its fields.

Six XML sources go into the Unmarshaller. The sources are File, StringReader, InputStream, URL, DOM Node and XMLStreamReader. The Unmarshaller parses the XML and validates it when a schema is set. Then the Unmarshaller matches the root element to an @XmlRootElement class and fills the fields. unmarshal(source) returns an Object that we cast to Song. unmarshal(source, Track.class) returns a JAXBElement of Track. Each problem becomes a ValidationEvent, which goes to the event handler.
unmarshal() accepts many kinds of XML input. The root element decides which class JAXB creates.

1.1. The Two Maven Dependencies

Jakarta XML Binding comes in two parts, an API and an implementation. Our code compiles against the API, and the implementation does the XML parsing when the program runs.

  • The API jar jakarta.xml.bind:jakarta.xml.bind-api holds the interfaces and annotations of the jakarta.xml.bind package, such as JAXBContext, Unmarshaller and @XmlRootElement.
  • The runtime jar org.glassfish.jaxb:jaxb-runtime is the reference implementation, the official one from the Eclipse JAXB RI project. The runtime scope is enough here because our code never imports a class from this jar.

With only the API jar, the project compiles, but the first JAXBContext.newInstance() call throws a JAXBException.

jakarta.xml.bind.JAXBException: Implementation of Jakarta XML Binding-API has not been found on module path or classpath.
 - with linked exception:
[java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory]

Older posts use com.sun.xml.bind:jaxb-impl, which is the same reference implementation packaged differently. Either one works, but we pick jaxb-runtime for new projects. Spring Boot manages the versions of both dependencies, so in a Spring Boot project we can leave out the version element.

2. JAXB Unmarshaller Example

The following example reads songs from XML. The complete code is in the Core-Java repository on GitHub, where the UnmarshallerDemo class prints every result, compiled to Java 21 bytecode and run on JDK 25 with Jakarta XML Binding API 4.0.5 and jaxb-runtime 4.0.9.

2.1. The Song Class and the XML File

The file song.xml holds one song, and its root element song has an id attribute and three child elements.

<?xml version="1.0" encoding="UTF-8"?>
<song id="7">
  <title>Imagine</title>
  <artist>John Lennon</artist>
  <seconds>183</seconds>
</song>

The Song class matches that structure. @XmlRootElement tells JAXB that a song element can be the root of an XML document, and @XmlAccessorType(XmlAccessType.FIELD) tells JAXB to read and write the fields, so the class needs no setters. The other JAXB annotations change element names, order and access in the same way.

@XmlRootElement(name = "song")
@XmlAccessorType(XmlAccessType.FIELD)
public class Song {

  @XmlAttribute
  private int id;                 // <song id="7">

  private String title;           // <title>
  @XmlElement(defaultValue = "Unknown")   // see section 6
  private String artist;          // <artist>
  private int seconds;            // <seconds>

  public Song() { }               // JAXB calls the no-arg constructor

  // getters and toString()
}

JAXB creates the object through the no-arg constructor, so the class needs one, although the constructor may be private. A class without a no-arg constructor fails with the no default constructor found error.

2.2. Unmarshal an XML File

The method unmarshal() returns a plain Object, so we cast the result to the root class, here Song.

JAXBContext context = JAXBContext.newInstance(Song.class);
Unmarshaller unmarshaller = context.createUnmarshaller();

Song song = (Song) unmarshaller.unmarshal(new File("song.xml"));
// Song[id=7, title=Imagine, artist=John Lennon, seconds=183]

There is no overload that takes a java.nio.file.Path, so we call path.toFile() to turn the Path into a File first.

2.3. Unmarshal an XML String

Sometimes the XML is already in a String, for example the body of an HTTP response. In that case, we wrap the string in a StringReader and pass the reader, and a text block keeps the XML readable inside Java code.

String xml = """
    <song id="7">
      <title>Imagine</title>
      <artist>John Lennon</artist>
      <seconds>183</seconds>
    </song>""";

Song song = (Song) unmarshaller.unmarshal(new StringReader(xml));
// Song[id=7, title=Imagine, artist=John Lennon, seconds=183]

2.4. Unmarshal an InputStream or a URL

For bytes from a file, a socket or an upload, an InputStream is the better choice because the XML parser then reads the encoding from the XML declaration (the first line of the file). We close the stream ourselves with try-with-resources.

try (InputStream in = Files.newInputStream(Path.of("song.xml"))) {
  Song song = (Song) unmarshaller.unmarshal(in);
  // Song[id=7, title=Imagine, artist=John Lennon, seconds=183]
}

The URL overload opens the resource and reads the XML. A URL can point to a web server, but here it points to a file on the classpath, and we get it the same way we read a file from the resources folder.

URL url = UnmarshallerDemo.class.getResource("/jaxb/unmarshaller/song.xml");

Song song = (Song) unmarshaller.unmarshal(url);
// Song[id=7, title=Imagine, artist=John Lennon, seconds=183]

2.5. Unmarshal a DOM Node or an XMLStreamReader

Sometimes the XML is already parsed into a DOM tree, for example by the DOM parser. A DOM tree is the whole XML document held in memory as Java objects, and we can pass the Document or any element inside the tree. We also make the factory namespace aware, so the DOM keeps the namespace of each element, which matters for XML with namespaces (section 5).

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document document = dbf.newDocumentBuilder().parse(new File("song.xml"));

Song song = (Song) unmarshaller.unmarshal(document);
// Song[id=7, title=Imagine, artist=John Lennon, seconds=183]

A StAX XMLStreamReader reads the XML piece by piece, as a stream of events. We use it when we want to control the parser settings or when we read only a part of a large file.

XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
XMLStreamReader reader = xif.createXMLStreamReader(new StringReader(xml));

Song song = (Song) unmarshaller.unmarshal(reader);
// Song[id=7, title=Imagine, artist=John Lennon, seconds=183]

Unmarshaller also accepts a javax.xml.transform.Source, an org.xml.sax.InputSource and an XMLEventReader.

2.6. The JAXB.unmarshal() Shortcut

The jakarta.xml.bind.JAXB class has static helper methods. A single JAXB.unmarshal() call creates the context and the unmarshaller for us and returns the right type, so no cast is needed.

Song song = JAXB.unmarshal(new StringReader(xml), Song.class);
// Song[id=7, title=Imagine, artist=John Lennon, seconds=183]

The JAXB helper is fine for tests and scripts, but we avoid it in code that runs often because its JavaDoc says the performance “is not necessarily optimal” and points performance-critical code to the rest of the API. The helper also handles errors differently. It wraps them in an unchecked DataBindingException instead of the checked JAXBException, and it tries to keep reading past errors in the XML as long as possible.

3. Unmarshal XML Without @XmlRootElement Using JAXBElement

The xjc tool generates Java classes from an XSD schema (a file that describes the allowed XML). Classes generated by xjc often have no @XmlRootElement because the schema defines a type, not an element. Our Track class has the same shape.

@XmlAccessorType(XmlAccessType.FIELD)
public class Track {
  private String title;
  private int seconds;
}

The one-argument unmarshal() cannot find a class for the root element track, so it throws an UnmarshalException. The message lists the root elements the context knows, and (none) means the context has no @XmlRootElement class at all.

jakarta.xml.bind.UnmarshalException: unexpected element (uri:"", local:"track"). Expected elements are (none)

The two-argument unmarshal(source, Track.class) reads any root element into the given class. The call returns a JAXBElement, a small wrapper that holds the element name and the value. The two-argument overload accepts a Source, a Node, an XMLStreamReader or an XMLEventReader, so we wrap a plain string in a StreamSource.

String xml = "<track><title>Hey Jude</title><seconds>431</seconds></track>";

JAXBContext context = JAXBContext.newInstance(Track.class);
JAXBElement<Track> element = context.createUnmarshaller()
    .unmarshal(new StreamSource(new StringReader(xml)), Track.class);

QName name = element.getName();                             // track  (a QName)
Track track = element.getValue();                           // Track[title=Hey Jude, seconds=431]
String type = element.getDeclaredType().getSimpleName();    // Track

The same method also reads XML whose root name differs from the @XmlRootElement name, which is another case where we unmarshal without @XmlRootElement. The way back to XML works the same when we marshal without @XmlRootElement.

4. Validating XML Against an XSD Schema

By default, the Unmarshaller checks only that the XML is well formed (that is, every tag opens and closes correctly), so wrong values and missing elements pass without any error. Say a partner sends a song feed in which one entry has no title, and our code stores a song without a name. An XSD (XML Schema Definition) file prevents that because it lists the allowed elements, their order and their types, and the unmarshaller then rejects bad input. Our song.xsd requires a title and a positive seconds value.

<xs:element name="song">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="title" type="xs:string"/>
      <xs:element name="artist" type="xs:string" minOccurs="0"/>
      <xs:element name="seconds" type="xs:positiveInteger"/>
    </xs:sequence>
    <xs:attribute name="id" type="xs:int" use="required"/>
  </xs:complexType>
</xs:element>

4.1. Setting a Schema with setSchema()

We load the schema once with a SchemaFactory and pass the Schema object to setSchema(). Our test XML has two problems. The title is missing, and the seconds value is negative.

SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new File("song.xsd"));

Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setSchema(schema);

String invalidXml = """
    <song id="9">
      <artist>The Beatles</artist>
      <seconds>-5</seconds>
    </song>""";

Song song = (Song) unmarshaller.unmarshal(new StringReader(invalidXml));   // throws UnmarshalException

The unmarshaller stops at the first error and throws an UnmarshalException. The getMessage() of the UnmarshalException returns null, so we read the real error text from getLinkedException(), which also gives the line and the column.

e.getMessage()          -> null
e.getLinkedException()  -> org.xml.sax.SAXParseException; lineNumber: 2; columnNumber: 11; cvc-complex-type.2.4.a: Invalid content was found starting with element 'artist'. One of '{title}' is expected.

A Schema object is thread-safe, so one instance can serve all unmarshallers. We can also run XSD schema validation without JAXB. When we have the classes but no schema, generating an XSD from Java classes helps.

4.2. Collecting All Errors with a ValidationEventHandler

A ValidationEventHandler lets us see every problem, not only the first one. JAXB passes each problem to the handler as a ValidationEvent with a severity, a message and a location. The handler returns true to keep reading, or false to stop with an UnmarshalException, so a handler that returns true lets us report all problems of a document at once.

List<String> problems = new ArrayList<>();

unmarshaller.setEventHandler(event -> {
  ValidationEventLocator loc = event.getLocator();
  problems.add("line " + loc.getLineNumber() + ", column " + loc.getColumnNumber()
      + ": " + event.getMessage());
  return true;                              // continue with the next element
});

Song song = (Song) unmarshaller.unmarshal(new StringReader(invalidXml));
// Song[id=9, title=null, artist=The Beatles, seconds=-5]

The list holds three entries, and all three have the severity FATAL_ERROR.

line 2, column 11: cvc-complex-type.2.4.a: Invalid content was found starting with element 'artist'. One of '{title}' is expected.
line 3, column 24: cvc-minInclusive-valid: Value '-5' is not facet-valid with respect to minInclusive '1' for type 'positiveInteger'.
line 3, column 24: cvc-type.3.1.3: The value '-5' of element 'seconds' is not valid.

A handler that returns true gives back an object built from invalid data. Here, the object has seconds=-5, so we use the object only when the problem list is empty.

4.3. Strict Reading Without a Schema

Without a schema, JAXB still notices two kinds of problems. One is an element that the class does not map, such as <genre>, and the other is a value that does not fit the field type, such as two for an int field. The default handler ignores both, so both pass without an error.

Song withGenre = (Song) unmarshaller.unmarshal(new StringReader(
    "<song id=\"8\"><title>Yesterday</title><genre>pop</genre><seconds>125</seconds></song>"));
// Song[id=8, title=Yesterday, artist=null, seconds=125]     (genre ignored)

Song badNumber = (Song) unmarshaller.unmarshal(new StringReader(
    "<song id=\"8\"><title>Yesterday</title><seconds>two</seconds></song>"));
// Song[id=8, title=Yesterday, artist=null, seconds=0]       (no error!)

A handler that returns false turns both cases into an UnmarshalException, which makes it a cheap way to read strictly when no XSD exists.

unmarshaller.setEventHandler(event -> false);

// <genre> -> UnmarshalException: unexpected element (uri:"", local:"genre"). Expected elements are <{}seconds>,<{}artist>,<{}title>
// "two"   -> UnmarshalException: Not a number: two  (cause: java.lang.NumberFormatException)

5. Unmarshalling XML With Namespaces

An XML namespace is a URI that qualifies element names, much like a package name qualifies a Java class name. We declare it as xmlns=”…”, or with a prefix such as xmlns:m=”…”. JAXB compares both the namespace and the local name, so an element m:song in a namespace is a different element than a plain song. The XML in this section uses the namespace https://example.com/music.

<m:song xmlns:m="https://example.com/music">
  <m:title>Imagine</m:title>
  <m:seconds>183</m:seconds>
</m:song>

The same XML gives three different results with three different mappings.

MappingResult
Song with no namespaceUnmarshalException: unexpected element (uri:”https://example.com/music”, local:”song”). Expected elements are <{}song>
Only @XmlRootElement(name = “song”, namespace = “https://example.com/music”)RootOnlyNamespaceSong[title=null, seconds=0], no error
@XmlSchema in package-info.javaMusicSong[title=Imagine, seconds=183]

The second row is a common trap. The root element matches, but JAXB still expects the child elements without a namespace, so it skips them and the fields stay empty. The @XmlSchema annotation on the package sets the namespace for every element of the package, and elementFormDefault = QUALIFIED applies it to the child elements too.

@XmlSchema(namespace = "https://example.com/music", elementFormDefault = XmlNsForm.QUALIFIED)
package com.howtodoinjava.jaxb.unmarshaller.ns;

import jakarta.xml.bind.annotation.XmlNsForm;
import jakarta.xml.bind.annotation.XmlSchema;

The prefix does not matter, so the same MusicSong class also reads <song xmlns=”https://example.com/music”>, which uses a default namespace with no prefix. Without a package-info.java, we can set namespace on every @XmlElement instead. To read single values from namespaced XML without classes, we use XPath namespace resolution.

6. Missing Elements, Empty Elements and Callbacks

JAXB treats a missing element and an empty element differently. A missing element leaves the field at its Java default (null, 0 or false), whereas for an empty element such as <artist/>, JAXB sets the value from @XmlElement(defaultValue = …) when the annotation has one.

@XmlElement(defaultValue = "Unknown")
private String artist;

// <title>Yesterday</title><seconds>125</seconds>           -> artist=null
// <title>Yesterday</title><artist/><seconds>125</seconds>  -> artist=Unknown

A class can also declare two callback methods, which the unmarshaller calls for us once for each object. The names and the parameters must match, but the methods may be private.

  • The method beforeUnmarshal(Unmarshaller, Object parent) runs after the constructor and before JAXB sets the fields.
  • The method afterUnmarshal(Unmarshaller, Object parent) runs after JAXB sets all fields, so it is a good place to check values or fill derived fields.
void beforeUnmarshal(Unmarshaller unmarshaller, Object parent) {
  System.out.println("beforeUnmarshal: " + this);
}

void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
  System.out.println("afterUnmarshal: " + this);
}
beforeUnmarshal: Song[id=0, title=null, artist=null, seconds=0]
afterUnmarshal: Song[id=7, title=Imagine, artist=John Lennon, seconds=183]

The parent argument is the object that contains the current one, and for the root element, parent is null. When we want one callback that sees every object, Unmarshaller.setListener() takes an Unmarshaller.Listener with the same two methods.

7. Migrating From javax.xml.bind to jakarta.xml.bind

JEP 320 removed JAXB from the JDK in Java 11, together with the other Java EE modules. The specification moved to the Eclipse Foundation under a new name, Jakarta XML Binding, and version 3.0 renamed the packages from javax.xml.bind to jakarta.xml.bind. The API is the same, and only the package names and the Maven coordinates change.

JAXB 2.x (old)Jakarta XML Binding 4.x
JDKBundled in Java 6 to 10Separate library since Java 11
Packagejavax.xml.bindjakarta.xml.bind
API artifactjavax.xml.bind:jaxb-apijakarta.xml.bind:jakarta.xml.bind-api 4.0.5
Implementationcom.sun.xml.bind:jaxb-impl 2.3.xorg.glassfish.jaxb:jaxb-runtime 4.0.9
Minimum Java611
Used byJava EE 8, Spring Boot 2Jakarta EE 10 and later, Spring Boot 3 and 4

On Java 11 and later, code that still imports javax.xml.bind fails at compile time when the old jaxb-api jar is missing.

error: package javax.xml.bind does not exist
import javax.xml.bind.JAXBContext;

The migration has two parts. We replace javax.xml.bind with jakarta.xml.bind in the imports, and we add the two new dependencies and remove the old javax.xml.bind:jaxb-api jar. We watch for a class that keeps a javax annotation while the context comes from jakarta, because the Jakarta runtime does not see the javax annotation, so unmarshalling fails with the same message as in section 3.

jakarta.xml.bind.UnmarshalException: unexpected element (uri:"", local:"song"). Expected elements are (none)

8. JAXB Unmarshaller FAQs

8.1. Is the JAXB Unmarshaller Thread-Safe?

No. JAXBContext is thread-safe, whereas Marshaller, Unmarshaller and Validator are not. So we share one JAXBContext and either create an Unmarshaller per request or keep one unmarshaller per thread in a ThreadLocal.

private static final JAXBContext CONTEXT = newContext(Song.class);   // created once

Song read(Reader xml) throws JAXBException {
  return (Song) CONTEXT.createUnmarshaller().unmarshal(xml);
}

8.2. How Do We Unmarshal a List of Objects?

We put the list into a wrapper class with a List field because JAXB needs one root element. For example, a Playlist class can hold @XmlElement(name = “song”) List<Song> songs, which is how we unmarshal a list or set of objects. We unmarshal a Map with a wrapper class too. To add an extra element around the list items, we use @XmlElementWrapper.

8.3. Is JAXB Unmarshalling Safe Against XXE Attacks?

Yes, by default, although we still turn off DTDs for untrusted input. In an XXE (XML External Entity) attack, the XML starts with a DOCTYPE that declares an entity (that is, a named placeholder that points to a local file or a URL). If the parser follows the entity, the content of that file ends up in our object. With jaxb-runtime 4.0.9, the default parser refuses the entity.

org.xml.sax.SAXParseException: External Entity: Failed to read external document 'secret14230076990306164254.txt', because 'file' access is not allowed due to restriction set by the accessExternalDTD property.

For XML from untrusted sources, we still turn off DTDs (the DOCTYPE part where entities are declared) completely. To do that, we unmarshal from an XMLStreamReader with SUPPORT_DTD set to false, as in section 2.5, and the parser then rejects the entity reference itself (The entity “x” was referenced, but not declared).

8.4. Which Properties Does the Unmarshaller Support?

None that the specification requires, unlike Marshaller, which has jaxb.formatted.output and others. The method setProperty() accepts only provider-specific names and throws a PropertyException for an unknown name. In practice, the settings we use are methods, namely setSchema(), setEventHandler(), setListener() and setAdapter(). The method setAdapter() takes an XmlAdapter instance (a class that converts a value between its XML form and a Java type), and an XmlAdapter also works with interfaces.

8.5. Can JAXB Read JSON Too?

Not the reference implementation, which reads XML only. EclipseLink MOXy, another JAXB implementation, also reads JSON with the same annotations, so we can convert JSON to a Java object with MOXy.

9. Conclusion

A JAXB Unmarshaller turns XML into Java objects, and one unmarshal() call reads a File, a StringReader, an InputStream, a URL or a DOM node. We keep one JAXBContext and create an Unmarshaller per call, and for classes without @XmlRootElement, we use unmarshal(source, Class) and JAXBElement. We also set a schema or an event handler, because without one the default unmarshaller skips unknown elements and invalid numbers without an error. On Java 11 and later, we use jakarta.xml.bind-api with jaxb-runtime.

The short version of the same task is to read XML to a Java object, and the root mapping depends on @XmlRootElement.

10. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.