Marshal & Unmarshal Generic Java Class to XML with JAXB
In this article, we will see how can we map a generic class to XML or vice versa. As a developer, it is very common to do this operation especially when you are doing integration with other systems. This method will help you to avoid a lot of unnecessary duplicated code and have a cleaner output.
Requirements: You will knowledge of java, XML, and JAXB
Nothing other than an example can make it easier for us to understand this. Imagine you need to call an API with the following XML structure for request and response.
Request for Product API endpoint:
<?xml version="1.0" encoding="UTF-8" ?>
<request>
<header>
<requestId>RIDXXXXXXXXXXX02</requestId>
</header>
<body>
<productName>Product Name</productName>
<price>119.99</price>
<quantity>1</quantity>
</body>
</request>
Response from Product API endpoint:
<?xml version="1.0" encoding="UTF-8" ?>
<response>
<header>
<requestId>RIDXXXXXXXXXXX02</requestId>
<status>SUCCESS</status>
</header>
<body>
<productId>1</productId>
<productName>Product Name</productName>
<price>119.99</price>
<quantity>1</quantity>
</body>
</response>
Request for Customer API endpoint:
<?xml version="1.0" encoding="UTF-8" ?>
<request>
<header>
<requestId>RIDXXXXXXXXXXX01</requestId>
</header>
<body>
<userName>John Doe</userName>
<age>28</age>
</body>
</request>
Response from Customer API endpoint:
<?xml version="1.0" encoding="UTF-8" ?>
<response>
<header>
<requestId>RIDXXXXXXXXXXX01</requestId>
<status>SUCCESS</status>
</header>
<body>
<userId>1</userId>
<userName>John Doe</userName>
<age>28</age>
</body>
</response>
We are going to use the JAXB to help us with mapping the XML to our objects and vice versa.
JAXB will provide the proper annotation to help you map the XML to the object and there are plenty of articles and tutorials that you can refer to get more information about it. In our case, the request and response object can have a body that can have a different XML structure.
The request class is a generic class with a body properly with type T. The class itself has 2 annotations including @XmlRootElement and @XmlAccessorType.
@XmlRootElement is used to specify to the JAXB context that this class can be a XML root element.
@XmlAccessorType defines the fields and properties of your Java classes that the JAXB engine uses for binding. It has four values: PUBLIC_MEMBER
, FIELD
, PROPERTY
and NONE
.
For properties, we have used these annotations based on our XML structure.
@XmlElement maps a field or property to an XML element.
@XmlAnyElement serves as a “catch-all” property while unmarshalling xml content into a instance of a JAXB annotated class. It typically annotates a multi-valued JavaBean property, but it can occur on single value JavaBean property. During unmarshalling, each xml element that does not match a static @XmlElement or @XmlElementRef annotation for the other JavaBean properties on the class, is added to this “catch-all” property.
@Data
@XmlRootElement(name = "request")
@XmlAccessorType(XmlAccessType.FIELD)
public class Request<T> {
@XmlElement( name = "header" )
Header header = new Header();
@XmlAnyElement( lax = true )
T body;
}
Now using the request object and related DTO object for customer and product you can map the object to XML as follow. It is important to have the class that is used to marshal as input when creating JAXB context objects. in our case there are Request.class, CustomerBody.class, ProductBody.class.
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Request.class, CustomerBody.class, ProductBody.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(marshaller.JAXB_FORMATTED_OUTPUT,true);
StringWriter customerXml = new StringWriter();
marshaller.marshal(customerRequest, customerXml);
System.out.println("Marshaled Customer : " + customerXml);
StringWriter productXml = new StringWriter();
marshaller.marshal(productRequest, productXml);
System.out.println("Marshaled Product : " + productXml);
} catch (JAXBException e) {
e.printStackTrace();
}
The same pattern can be used for unmarshalling using the generic Response object with the body properly as type T.
@Data
@XmlRootElement(name = "response")
@XmlAccessorType(XmlAccessType.FIELD)
public class Response<T> {
@XmlElement( name = "header" )
Header header = new Header();
@XmlAnyElement( lax = true )
T body;
}
Now using the below code you can unmarshal the XML into the object. In my case, the XML is stored in a String variable. It can be a file or any other input stream.
try {
String customerXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><response><header><requestId>RIDXXXXXXXXXXX01</requestId><status>SUCCESS</status></header><body><userId>1</userId><userName>John Doe</userName><age>28</age></body></response>";
String productXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><response><header><requestId>RIDXXXXXXXXXXX02</requestId><status>SUCCESS</status></header><body><productId>1</productId><productName>Product Name</productName><price>119.99</price><quantity>1</quantity></body></response>";
JAXBContext jaxbContextCustomer = JAXBContext.newInstance(Response.class, CustomerBody.class);
Unmarshaller customerUnmarshaller = jaxbContextCustomer.createUnmarshaller();
Response<CustomerBody> customerResponse = (Response<CustomerBody>) customerUnmarshaller.unmarshal(new StringReader(customerXml));
System.out.println(customerResponse);
JAXBContext jaxbContextProduct = JAXBContext.newInstance(Response.class, ProductBody.class);
Unmarshaller productUunmarshaller = jaxbContextProduct.createUnmarshaller();
Response<CustomerBody> productResponse = (Response<CustomerBody>) productUunmarshaller.unmarshal(new StringReader(productXml));
System.out.println(productResponse);
} catch (JAXBException e) {
e.printStackTrace();
}
I hope this article helps you write more clean codes. If you need a sample you can refer to this GitHub repository.