Reading and Writing XML in Java

What is XML?

The abbreviation "XML" stands for - eXtensible Markup Language. It has a markup structure similar to HTML and was designed to store and transport data. It defines a set of rules that make it both human- and machine-readable.

Despite being a markup language like HTML, XML is commonly being used to exchange data between web services, backends, and front-ends, much like JSON and is considered its predecessor.

If you're interested in reading about reading and writing JSON in Java, we've already got that covered!

It's important to note that XML doesn't have a predefined set of tags like HTML, but are rather user-defined. It's this flexibility that led to the creation of multiple document formats like RSS, Atom, SOAP, and XHTML. All of these formats are subsets of XML, in essence.

Let's look at a simple XML document, which replicates the same object we used earlier with regards to JSON:

<?xml version="1.0" encoding="UTF-8"?>  
<person>  
    <age>31</age>
    <hobbies>
        <element>Football</element>
        <element>Swimming</element>
    </hobbies>
    <isMarried>true</isMarried>
    <kids>
        <person>
            <age>5</age>
            <name>Billy</name>
        </person>
        <person>
            <age>3</age>
            <name>Milly</name>
        </person>
    </kids>
    <name>Benjamin Watson</name>
</person>  

The key difference between XML and JSON is that we're defining this file with the XML version and encoding in the beginning of the document with an <?xml> tag. Another difference is that each object property must be wrapped in its own tag - <age>31</age>. Array elements can't be specified without a tag, thus to list them, we're wrapping them up with <element>...</element> within the <hobbies>...</hobbies> tag.

JAXB

As XML is a text-based format, you could use the same techniques to read and write it as any other text file.

Java, however, provides a convenient way of manipulating XML using the framework called Java Architecture for XML Binding, or JAXB for short. It allows us to map Java object to XML documents and vice versa. JAXB was first introduced in JDK 1.6 and isn't available in prior versions.

As JAXB is a standard JDK framework, there's no need to include any external dependencies to the project for JDK 1.6+.

*Note: *If you're using Java 9 or higher though, you should include an additional parameter to the javac command. If you're using an IDE like IntelliJ IDEA or Eclipse, look for an additional compiler options setting and make sure it includes the --add-modules java.xml.bind string.

In case of IntelliJ IDEA it is located at Preferences -> Build, Execution, Deployment -> Compiler -> Java Compiler menu.

If you'd be still getting errors like Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext even after adding the extra compiler option, then add the following Maven dependencies:

<dependency>  
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.2.11</version>
</dependency>  
<dependency>  
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-core</artifactId>
    <version>2.2.11</version>
</dependency>  
<dependency>  
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-impl</artifactId>
    <version>2.2.11</version>
</dependency>  
<dependency>  
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
</dependency>  

Core JAXB concepts are named Marshalling and Unmarshaling. They are, unsurprisingly, represented by the classes Marshaller and Unmarshaller.

Marshalling is the process of converting Java objects into XML, and Unmarshalling is the process of converting XML into Java objects.

JAXB is configured using annotations that are imported from the javax.xml.bind.annotations package.

Let's define a Java class that represents the person described in our XML document:

@XmlRootElement
public class Person {

    public Person(String name, int age, boolean isMarried, List<String> hobbies, List<Person> kids) {
        this.name = name;
        this.age = age;
        this.isMarried = isMarried;
        this.hobbies = hobbies;
        this.kids = kids;
    }

    public Person(String name, int age) {
        this(name, age, false, null, null);
    }

    private String name;
    private Integer age;
    private Boolean isMarried;
    private List<String> hobbies;
    private List<Person> kids;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public boolean isMarried() {
        return isMarried;
    }

    @XmlElement(name = "isMarried")
    public void setMarried(boolean married) {
        isMarried = married;
    }

    @XmlElementWrapper(name = "hobbies")
    @XmlElement(name = "element")
    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    public List<Person> getKids() {
        return kids;
    }

    @XmlElementWrapper(name = "kids")
    @XmlElement(name = "person")
    public void setKids(List<Person> kids) {
        this.kids = kids;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", isMarried=" + isMarried +
                ", hobbies=" + hobbies +
                ", kids=" + kids +
                '}';
    }
}

@XmlRootElement - maps a class or an enum type to an XML element. It describes the root element of the XML document and should be specified at the Person class declaration.

@XmlElementWrapper - generates a wrapper element around the XML representation, a List in our case. The elements of the list should be specified explicitly using the @XMLElement annotation.

@XMLElement - maps a property from a Java object to an XML element derived from the property name. To specify a different XML property name, we're including it as a String parameter to the annotation declaration, i.e. (name = "person").

Unmarshalling

The simplest example of the marshalling technique will require from us to create a JAXBContext instance, passing a Person.class as the only input parameter of its constructor.

The unmarshaller is then created by calling a createUnmarshaller() method, and an instance of the actual Person is generated by its unmarshal() method.

Make sure to use explicit typecast, as unmarshal method returns type Object:

public class Solution {  
    public static void main(String[] args) throws Exception {
        Person person = XMLtoPersonExample("person.xml");
        System.out.println(person);
    }

    private static Person XMLtoPersonExample(String filename) throws Exception {
        File file = new File(filename);
        JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);

        Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
        return (Person) jaxbUnmarshaller.unmarshal(file);
    }
}

After running this code, you should see something along the lines of:

Person{name='Benjamin Watson', age=31, isMarried=true, hobbies=[Football, Swimming], kids=[Person{name='Billy', age=5, isMarried=null, hobbies=null, kids=null}, Person{name='Milly', age=3, isMarried=null, hobbies=null, kids=null}]}  

Marshalling

To demonstrate the JAXB's ability to write an XML file using the Java object as a source, we'll add the following method:

private static void personToXMLExample(String filename, Person person) throws Exception {  
    File file = new File(filename);
    JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);

    Marshaller jaxbMarshaller = jaxbContext.createMarshaller();

    jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    jaxbMarshaller.marshal(person, file);
    jaxbMarshaller.marshal(person, System.out);
}

It's very similar to the previous example and includes creating a JAXBContext again. This time though, the process will go in the reverse direction, and the XML output will be written to the file and the console.

By adding an invocation of this method as the last string in the Solution.main() like:

personToXMLExample("person-output.xml", person);  

and running it, we'll get an unfortunate exception.

Exception in thread "main" java.lang.NullPointerException  
    at com.stackabuse.xml.Person.isMarried(Person.java:49)
    at com.stackabuse.xml.Person$JaxbAccessorM_isMarried_setMarried_boolean.get(MethodAccessor_Boolean.java:61)
...

We've made a mistake by setting the isMarried field type to the wrapper class Boolean and the return type of the getter isMarried() to primitive boolean, which leads to JAXB trying to unbox null and throwing a NullPointerException as a result of it.

A quick and easy fix to this would be to align those two to either boolean or Boolean.

After fixing the issue, we'll get the following output to both the console and the file:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
<person>  
    <age>31</age>
    <hobbies>
        <element>Football</element>
        <element>Swimming</element>
    </hobbies>
    <kids>
        <person>
            <age>5</age>
            <name>Billy</name>
        </person>
        <person>
            <age>3</age>
            <name>Milly</name>
        </person>
    </kids>
    <isMarried>true</isMarried>
    <name>Benjamin Watson</name>
</person>  

As we see, it's fully identical to the orginal XML file that we marshalled into the person object.

Conclusion

Reading and writing XML in Java could be easily accomplished by using the JAXB framework. Using annotations, we define the mapping rules between Java classes and XML documents that represent the same objects.

XML is often considered an outdated format which is inferior to JSON. However, knowing how to read and write it using Java, is a useful skill for any software developer as many of the services on the web are still using it and don't yet have a JSON API. This is also the case for many file formats that store data in XML-formatted files.

Although, in case JSON is more your thing, I'd suggest reading about reading and writing JSON to Java, we've got that covered too!

Author image
Ukraine, Kiev Twitter Github