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 objects 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 the 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
.
Marshaling is the process of converting Java objects into XML, and Unmarshaling 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")
.
Unmarshaling
The simplest example of the marshaling technique will require us to create a JAXBContext
instance, passing a Person.class
as the only input parameter of its constructor.
The un-marshaler 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:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
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}]}
Marshaling
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 original XML file that we marshaled 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 their 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!