Java, XML

Parsing XML using JAXB in Java

JAXB stands for Java Architecture for XML Binding. JAXB is used to convert XML into a Java object and vice versa. In this article, we will look into converting a XML into a Java object.

Here is the sample XML file ‘Cart.xml’ located in /resources folder of the project:

Cart.xml

<?xml version="1.0" encoding="UTF-8"?>
<Cart>
	<StoreId>8847454</StoreId>
	<StoreName>Target Junction Road</StoreName>>
	<Items>
		<Item ItemId="1234">
			<ItemType>OTHER</ItemType>
			<Description>Book</Description>
			<Quantity>1</Quantity>
			<Price>12.49</Price>
		</Item>
		<Item ItemId="2345">
			<ItemType>MEDICINE</ItemType>
			<Description>Ibuprofen</Description>
			<Quantity>1</Quantity>
			<Price>7.99</Price>
		</Item>
		<Item ItemId="5678">
			<ItemType>FOOD</ItemType>
			<Description>Chocolate Bar</Description>
			<Quantity>1</Quantity>
			<Price>0.85</Price>
		</Item>
	</Items>
</Cart>

Now in order to convert the above XML into a Java object, we will have to create required Java objects. For the above XML, we will need ‘Cart’ and ‘Item’ objects.

Cart.java

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="Cart")
public class Cart implements Serializable {
	
	private static final long serialVersionUID = 1L;

	@XmlElement(name="StoreId")
	private long storeId;
	
	@XmlElement(name="StoreName")
	private String storeName;
	
	@XmlElementWrapper(name="Items")
	@XmlElement(name="Item")
	private List<Item> items = new ArrayList<Item>();

	public long getStoreId() {
		return storeId;
	}

	public void setStoreId(long storeId) {
		this.storeId = storeId;
	}

	public String getStoreName() {
		return storeName;
	}

	public void setStoreName(String storeName) {
		this.storeName = storeName;
	}

	public List<Item> getItems() {
		return items;
	}

	public void setItems(List<Item> items) {
		this.items = items;
	}
}

Item.java

import java.io.Serializable;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="Item")
public class Item implements Serializable {
	
	private static final long serialVersionUID = 1L;

	@XmlAttribute(name="ItemId")
	private long itemId;
	
	@XmlElement(name="ItemType")
	private String itemType;
	
	@XmlElement(name="Description")
	private String description;
	
	@XmlElement(name="Quantity")
	private int quantity;
	
	@XmlElement(name="Price")
	private float price;
	
	public long getItemId() {
		return itemId;
	}
	
	public void setItemId(long itemId) {
		this.itemId = itemId;
	}
	
	public String getItemType() {
		return itemType;
	}

	public void setItemType(String itemType) {
		this.itemType = itemType;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public int getQuantity() {
		return quantity;
	}

	public void setQuantity(int quantity) {
		this.quantity = quantity;
	}

	public float getPrice() {
		return price;
	}

	public void setPrice(float price) {
		this.price = price;
	}
}

The last step is to unmarshal the XML file using JAXB, and create Cart and Item Java objects.

	public static void main(String[] args) {
		
		try {
			File file = new File("resources/Cart.xml");
			JAXBContext jaxbContext = JAXBContext.newInstance(Cart.class);
			Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
			Cart cart = (Cart) unmarshaller.unmarshal(file);
			printReceipt(cart);
		} catch (JAXBException e) {
			e.printStackTrace();
		}
	}
	
	private static void printReceipt(Cart cart) {
		
		System.out.println(cart.getStoreId());
		System.out.println(cart.getStoreName());
		
		for(Item item : cart.getItems()) {
			
			System.out.println(item.getItemId());
			System.out.println(item.getItemType());
			System.out.println(item.getDescription());
			System.out.println("$" +item.getPrice());
			System.out.println(item.getQuantity());
		}
	}

Output:
8847454
Target Junction Road
1234
OTHER
Book
$12.49
1
2345
MEDICINE
Ibuprofen
$7.99
1
5678
FOOD
Chocolate Bar
$0.85
1

Java, XML

SAX parser not getting full text in Java

In one of my applications, I am using SAX Parser to read a XML file in Java. For the most part, the SAX Parser worked just fine. However, recently I ran into an issue with my SAX Parser. I discovered that for large texts between the tags, the SAX Parser was not returning the full text. That is, the characters() method in my handler class was returning only some portion of the text but not the full text.

While looking into the issue I found that this is how the SAX Parser is set up. A SAX Parser can return anything from a single character at a time up to several thousand and still be a standard conforming implementation. In other words, it is not required that the SAX Parser will invoke the characters() method only once per tag. It could invoke characters() method just once or multiple times per tag. Its totally random.

Now that I had found my problem, I just had to accumulate all the characters and display them when I was sure that all of them have been found. In other words, I had to maintain a StringBuilder, and keep appending to the StringBuilder until I encounter the end tag. Here is how my new implementation looked like:


	private StringBuilder sb;
	
	@Override
	public void startElement(
			String uri, 
			String localName,
			String qName, 
			Attributes attributes
	) throws SAXException {
		
		student = new Student();
		sb = new StringBuilder();
	}
	
	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		
		sb.append(ch, start, length);
		student.setFirstName(sb.toString());
	}
Java, XML

Read XML file using SAX parser in Java

You can parse an XML file in 2 ways:

1. Using SAX Parser
2. Using DOM Parser

The DOM parser works pretty well for small XML files. However, when you have an XML file with a lot of data, it is advisable to use SAX Parser. For large XML files, SAX is faster than DOM because it does not load the XML document into the memory, its an event based.

In this article, I will use SAX Parser to read the XML file and transform the content to Java objects.

The StudentXML.xml file looks like this:


<School name="ABPS High School">
	<Student id="1234">
		<FirstName>Devesh</FirstName>
		<LastName>Sharma</LastName>
		<Age>27</Age>
		<Gender>Male</Gender>
	</Student>
	<Student id="5678">
		<FirstName>Jeff</FirstName>
		<LastName>Doe</LastName>
		<Age>42</Age>
		<Gender>Male</Gender>
	</Student>
	<Student id="4567">
		<FirstName>Megan</FirstName>
		<LastName>Ray</LastName>
		<Age>22</Age>
		<Gender>Female</Gender>
	</Student>
</School>

When using a SAX Parser, we need to make a Handler class that extends the org.xml.sax.helpers.DefaultHandler class. This Handler class overrides a few methods of the super class. These methods are startElement(), endElement() and characters(). Basically, SAX Parser reads the XML file from start to end, and whenever it finds an event (tag, text, etc) it will invoke the corresponding event handling method.

Also, in order to transform the content of XML file to Java objects, you will have to create entity classes. For the given StudentXML.xml file, you will need 2 entity classes, namely School.java and Student.java.

The School.java class looks like this:

import java.util.ArrayList;
import java.util.List;

public class School {

	private String name;
	private List<Student> students = new ArrayList<Student>();
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public List<Student> getStudents() {
		return students;
	}
	
	public void setStudents(List<Student> students) {
		this.students = students;
	}
	
	public void addStudent(Student student) {
		this.students.add(student);
	}
}

The Student.java looks like this:

public class Student {

	private String studentId;
	private String firstName;
	private String lastName;
	private String age;
	private String gender;
	
	public String getStudentId() {
		return studentId;
	}

	public void setStudentId(String studentId) {
		this.studentId = studentId;
	}

	public String getFirstName() {
		return firstName;
	}
	
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
	
	public String getLastName() {
		return lastName;
	}
	
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	
	public String getAge() {
		return age;
	}
	
	public void setAge(String age) {
		this.age = age;
	}
	
	public String getGender() {
		return gender;
	}
	
	public void setGender(String gender) {
		this.gender = gender;
	}
}

The StudentHandler.java handler class looks like this:

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class StudentHandler extends DefaultHandler {

	private final String SCHOOL_TAG = "School";
	private final String NAME_ATTRIBUTE = "name";
	private final String STUDENT_TAG = "Student";
	private final String ID_ATTRIBUTE = "id";
	private final String FIRST_NAME_TAG = "FirstName";
	private final String LAST_NAME_TAG = "LastName";
	private final String AGE_TAG = "Age";
	private final String GENDER_TAG = "Gender";
	
	private boolean schoolFlag = false;
	private boolean studentFlag = false;
	private boolean firstNameFlag = false;
	private boolean lastNameFlag = false;
	private boolean ageFlag = false;
	private boolean genderFlag = false;
	
	private School school;
	private Student student;
	
	public void startElement(
			String uri, 
			String localName,
			String qName, 
			Attributes attributes
	) throws SAXException {
		
		if(qName.equalsIgnoreCase(SCHOOL_TAG)) {
			
			schoolFlag = true;
			school = new School();
			school.setName(attributes.getValue(NAME_ATTRIBUTE));
		}
		
		if(qName.equalsIgnoreCase(STUDENT_TAG)) {
			
			studentFlag = true;
			student = new Student();
			student.setStudentId(attributes.getValue(ID_ATTRIBUTE));
		}
		
		if(qName.equalsIgnoreCase(FIRST_NAME_TAG)) {
			
			if(!studentFlag || student == null) {
				throw new SAXException("Not a valid Student");
			}
			
			firstNameFlag = true;
		}
		
		if(qName.equalsIgnoreCase(LAST_NAME_TAG)) {
			
			if(!studentFlag || student == null) {
				throw new SAXException("Not a valid Student");
			}
			
			lastNameFlag = true;
		}
		
		if(qName.equalsIgnoreCase(AGE_TAG)) {
			
			if(!studentFlag || student == null) {
				throw new SAXException("Not a valid Student");
			}
			
			ageFlag = true;
		}
		
		if(qName.equalsIgnoreCase(GENDER_TAG)) {
			
			if(!studentFlag || student == null) {
				throw new SAXException("Not a valid Student");
			}
			
			genderFlag = true;
		}
	}

	public void endElement(
			String uri, 
			String localName, 
			String qName
	) throws SAXException {

		if(qName.equalsIgnoreCase(SCHOOL_TAG)) {
			schoolFlag = false;
		}
		
		if(qName.equalsIgnoreCase(STUDENT_TAG)) {
			school.addStudent(student);
			studentFlag = false;
		}
		
		if(qName.equalsIgnoreCase(FIRST_NAME_TAG)) {
			firstNameFlag = false;
		}
		
		if(qName.equalsIgnoreCase(LAST_NAME_TAG)) {
			lastNameFlag = false;
		}
		
		if(qName.equalsIgnoreCase(AGE_TAG)) {
			ageFlag = false;
		}
		
		if(qName.equalsIgnoreCase(GENDER_TAG)) {
			genderFlag = false;
		}
	}
	
	public void characters(
			char[] buffer, 
			int start, 
			int length
	) throws SAXException {
		
		String firstName;
		String lastName;
		String age;
		String gender;
		
		if(firstNameFlag) {
			firstName = new String(buffer, start, length);
			student.setFirstName(firstName);
		}
		
		if(lastNameFlag) {
			lastName = new String(buffer, start, length);
			student.setLastName(lastName);
		}
		
		if(ageFlag) {
			age = new String(buffer, start, length);
			student.setAge(age);
		}
		
		if(genderFlag) {
			gender = new String(buffer, start, length);
			student.setGender(gender);
		}
	}
	
	public School getSchool() {
		return school;
	}
}

Lastly, set things up for parsing in the main method.

	public static void main(String[] args) {

		try {
			SAXParserFactory factory = SAXParserFactory.newInstance();
			SAXParser saxParser = factory.newSAXParser();
			
			StudentHandler studentHandler = new StudentHandler();
			
			saxParser.parse("C:/Users/devesh_sharma/Documents/StudentXML.xml", studentHandler);
			
			School school = studentHandler.getSchool();
			
			System.out.println("School Name: " +school.getName());
			System.out.println("Number of students: " +school.getStudents().size());
			System.out.println();
			System.out.println("Students are as follows: ");
			System.out.println();
			
			for(int i=0; i< school.getStudents().size(); i++) {
				
				System.out.println("Student Id: " +school.getStudents().get(i).getStudentId());
				System.out.println("First Name: " +school.getStudents().get(i).getFirstName());
				System.out.println("Last Name: " +school.getStudents().get(i).getLastName());
				System.out.println("Age: " +school.getStudents().get(i).getAge());
				System.out.println("Gender: " +school.getStudents().get(i).getGender());
				System.out.println();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

Output:

School Name: ABPS High School
Number of students: 3

Students are as follows:

Student Id: 1234
First Name: Devesh
Last Name: Sharma
Age: 27
Gender: Male

Student Id: 5678
First Name: Jeff
Last Name: Doe
Age: 42
Gender: Male

Student Id: 4567
First Name: Megan
Last Name: Ray
Age: 22
Gender: Female

Hope this helps! 🙂

UPDATE – July 25, 2013: If you are using the above implementation of the SAX Parser, then you might encounter an issue wherein the SAX Parser doesn’t return the full text between the tags. Please refer this post to know more about the issue and the fix.