JAXB2 Schema Validation Example

This post shown how JAXB2 Schema validation can be used in your project, validating the generated XML against specific XML Schema Definition [XSD].

An XML Schema Definition describes the structure of an XML document and governs the rules and constraints being applied on XML content. JAXB2 provides API to validate the generated XML against an XSD in order to verify the correctness of generated XML.

There are choices available to perform Schema Validation

  • Using Marshaller
  • Using javax.xml.validation.Validator

Below is the XSD file [“edu.xsd”] used to validate the XML generated via Student & University mapping example. Please note that Section element in below XSD contains a minLength constraint(4 chars). We will verify the validation against this constraint.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
	
	<xs:element name="Student">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="firstName" type="xs:string" />
				<xs:element name="lastName" type="xs:string"  />
				<xs:element name="birthDate" type="xs:date" />
				<xs:element name="section" >
					<xs:simpleType>
						<xs:restriction base="xs:string">
							<xs:minLength value="4"/>
						</xs:restriction>
					</xs:simpleType>
				</xs:element>
			</xs:sequence>
			<xs:attribute name="id" type="xs:int" />
		</xs:complexType>
	</xs:element>
	
	<xs:element name="University">
		<xs:annotation>
			<xs:documentation>University Details</xs:documentation>
		</xs:annotation>
		<xs:complexType>
			<xs:sequence>
				<xs:element name="name" type="xs:string" />
				<xs:element name="address" type="xs:string" />
				<xs:element name="Students">
					<xs:complexType>
						<xs:sequence>
							<xs:element ref="Student"  minOccurs="1" maxOccurs="unbounded"/>
						</xs:sequence>
					</xs:complexType>
				</xs:element>
			</xs:sequence>
		</xs:complexType>
	</xs:element>

	
</xs:schema>

Following steps are involved in validation:

1) Create JAXB Context

		// create JAXB context
		JAXBContext context = JAXBContext.newInstance(Student.class);

2) Create Schema object

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

3) Validate using Marshaller

Create Marshaller to Generate XML from mapped and populated Java objects using JAXBContext created above. Set the Marshaller schema to the one created above.

		// create Marshaller using JAXB context
		Marshaller m = context.createMarshaller();
		// To format the [to be]generated XML output
		m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
	    
		//Set Schema
		m.setSchema( schema );
	    
		// Marshall the mapped object and write output to System.out [or to a file]
		m.marshal(university, System.out);
		m.marshal(university, new File(SOME_FILE));

4) Alternatiively, Validate using Validator

Use Javax.xml.validation.Validator class to validate.

	    JAXBSource source = new JAXBSource(context, university);
	    Validator validator = schema.newValidator();
	    validator.setErrorHandler(new CustomValidationErrorHandler());
	    validator.validate(source);

Provide an ErrorHandler implementation required by Validator.

package com.websystique.xml.model;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class CustomValidationErrorHandler implements ErrorHandler{
	
    public void warning(SAXParseException exception) throws SAXException {
        System.out.println("WARNING Occured");
        exception.printStackTrace();
    }
 
    public void error(SAXParseException exception) throws SAXException {
        System.out.println("ERROR Occured");
        exception.printStackTrace();
    }
 
    public void fatalError(SAXParseException exception) throws SAXException {
        System.out.println("FATAL ERROR Occured");
        exception.printStackTrace();
    }

}

That’s it. In case the generated XML does not comply with specified schema , Marshaller/Validator will throw error and eventually fail.

In our example, section property in Student class contains a minLength (4) constraints. In case the populated object is not meeting this constraint:

s1.setSection("Com");

then Marshaller will generate following error:

Exception in thread "main" javax.xml.bind.MarshalException
 - with linked exception:
[org.xml.sax.SAXParseException: cvc-minLength-valid: Value 'Com' with length = '3' is not facet-valid with respect to minLength '4' for type '#AnonType_sectionStudent'.]
	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:317)
	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243)
	at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75)
	at com.websystique.xml.JaxbSchemaValidation.main(JaxbSchemaValidation.java:78)
Caused by: org.xml.sax.SAXParseException: cvc-minLength-valid: Value 'Com' with length = '3' is not facet-valid with respect to minLength '4' for type '#AnonType_sectionStudent'.
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:195)
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:131)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:384)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:318)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:423)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:3188)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.elementLocallyValidType(XMLSchemaValidator.java:3103)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processElementContent(XMLSchemaValidator.java:3013)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleEndElement(XMLSchemaValidator.java:2156)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.endElement(XMLSchemaValidator.java:824)
	at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.endElement(ValidatorHandlerImpl.java:565)
	at org.xml.sax.helpers.XMLFilterImpl.endElement(XMLFilterImpl.java:546)
	at com.sun.xml.internal.bind.v2.runtime.output.SAXOutput.endTag(SAXOutput.java:117)
	at com.sun.xml.internal.bind.v2.runtime.output.XmlOutputAbstractImpl.endTag(XmlOutputAbstractImpl.java:109)
	at com.sun.xml.internal.bind.v2.runtime.output.ForkXmlOutput.endTag(ForkXmlOutput.java:76)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:294)
	at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:179)
	at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:166)
	at com.sun.xml.internal.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:239)
	at com.sun.xml.internal.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:87)
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:664)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:54)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:157)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:141)
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:561)
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:290)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:462)
	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:314)
	... 3 more

That’s all with validations. Below is the complete example for this post.


Complete Code Example

Following technologies being used:

  • Maven 3
  • JDK 1.6.0_45
  • Joda Time 2.7
  • Eclipse JUNO Service Release 2

Let’s begin.

Step 1: Create Project Directory Structure

Following will be the final project directory structure for this example:

Now let’s add the content mentioned in above structure explaining each in detail.

Step 2: Provide Dependencies in Maven pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.websystique.xml</groupId>
	<artifactId>JaxbBasicDemo</artifactId>
	<version>1.0.0</version>
	<packaging>jar</packaging>

	<name>JaxbBasicDemo</name>

	<properties>
		<joda-time.version>2.7</joda-time.version>
	</properties>

	<dependencies>
		<dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>${joda-time.version}</version>
        </dependency>
	</dependencies>
</project>

There is no special dependencies to be declared. Since i am using Joda Time for any date time related processing (and also to show an important feature of JAXB), i’ll declare that here. You can skip it if you prefer to use Java Date api for the same.

Step 3: Create XSD/SCHEMA for validation purpose

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
	
	<xs:element name="Student">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="firstName" type="xs:string" />
				<xs:element name="lastName" type="xs:string"  />
				<xs:element name="birthDate" type="xs:date" />
				<xs:element name="section" >
					<xs:simpleType>
						<xs:restriction base="xs:string">
							<xs:minLength value="4"/>
						</xs:restriction>
					</xs:simpleType>
				</xs:element>
			</xs:sequence>
			<xs:attribute name="id" type="xs:int" />
		</xs:complexType>
	</xs:element>
	
	<xs:element name="University">
		<xs:annotation>
			<xs:documentation>University Details</xs:documentation>
		</xs:annotation>
		<xs:complexType>
			<xs:sequence>
				<xs:element name="name" type="xs:string" />
				<xs:element name="address" type="xs:string" />
				<xs:element name="Students">
					<xs:complexType>
						<xs:sequence>
							<xs:element ref="Student"  minOccurs="1" maxOccurs="unbounded"/>
						</xs:sequence>
					</xs:complexType>
				</xs:element>
			</xs:sequence>
		</xs:complexType>
	</xs:element>

</xs:schema>

Step 4: Create Model Classes for Marsahlling/UnMarshalling

Create Sample Java classes [POJOs], and annotate them with appropriate JAXB annotations, in order to provide mapping between Java and plain XML.

com.websystique.xml.model.Student

package com.websystique.xml.model;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import org.joda.time.LocalDate;

@XmlRootElement(name = "Student")
@XmlType(propOrder = { "firstName", "lastName", "dob", "section" })
public class Student {

	private int id;
	private String firstName;
	private String lastName;
	private LocalDate dob;
	private String section;

	@XmlAttribute(name = "id")
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	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 getSection() {
		return section;
	}

	public void setSection(String section) {
		this.section = section;
	}

	@XmlElement(name = "birthDate")
	@XmlJavaTypeAdapter(LocalDateAdapter.class)
	public LocalDate getDob() {
		return dob;
	}

	public void setDob(LocalDate dob) {
		this.dob = dob;
	}

	@Override
	public String toString() {
		return "Student [id=" + id + ", firstName=" + firstName + ", lastName="
				+ lastName + ", dob=" + dob + ", section=" + section + "]";
	}

}

com.websystique.xml.model.University

package com.websystique.xml.model;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement(name = "University")
@XmlType(propOrder = { "name", "address", "students" })
public class University {

	List<Student> students;

	private String name;

	private String address;

	@XmlElementWrapper(name = "Students")
	@XmlElement(name = "Student")
	public List<Student> getStudents() {
		return students;
	}

	public void setStudents(List<Student> students) {
		this.students = students;
	}

	public String getName() {
		return name;
	}

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

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

}

This post depicts an example of Student and University relation. Each student can have several properties. A University can have several properties and additionally multiple students.

Student and University classes[both are pojo’s] and there properties, are annotated with JAXB annotations. These annotations tells JAXB which property of a Java object will be mapped to which element or attribute in XML.

@XmlRootElement can be used to map a java class to the root of XML document produced by that class. Once applied on a class, all properties of that class maps to XML elements. You can however , use @XmlElement on individual property to override default behavior( for example specify an Element with different name in XML than in java, specify required/optional/nillable attributes).For example Student.dob will be mapped to birthDate in XML.

@XmlElementWrapper can be used to create a wrapper around a collection of elements.
@XmlType can be used to specify the order in which the properties of a java object will appear in XML document.
@XmlAttribute can be used to map a property to an XML attribute.

Note that @XmlAttribute and @XmlElement can be applied on field level or on method level. In our case we are using them on methods.

Step 5: Create Adapters [Optional]

Adapters are used to convert java data types into XML types and vice versa. You may have noticed that we are using Joda-time LocalDate class to specify dob property in Student class. But JAXB knows nothing about LocalDate. So we need to provide a way to help JAXB map this property into xml format. That’s where XmlAdapter comes into play. Using XmlAdapter you can convert Java Type into XML type. In our case we are converting LocalDate to String format. We can specify the adapter to be used using @XmlJavaTypeAdapter on respective property in java.

com.websystique.xml.model.LocalDateAdapter

package com.websystique.xml.model;

import javax.xml.bind.annotation.adapters.XmlAdapter;

import org.joda.time.LocalDate;

public class LocalDateAdapter extends XmlAdapter<String, LocalDate> {

	public LocalDate unmarshal(String v) throws Exception {
		return new LocalDate(v);
	}

	public String marshal(LocalDate v) throws Exception {
		return v.toString();
	}

}

Above class provides a way to convert a LocalDate type into String and viceversa.

Step 6: Create a CustomErrorHandler

Create a Custom error handler to be used by Validator during schema validation.

package com.websystique.xml.model;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class CustomValidationErrorHandler implements ErrorHandler{
	
    public void warning(SAXParseException exception) throws SAXException {
        System.out.println("WARNING Occured");
        exception.printStackTrace();
    }
 
    public void error(SAXParseException exception) throws SAXException {
        System.out.println("ERROR Occured");
        exception.printStackTrace();
    }
 
    public void fatalError(SAXParseException exception) throws SAXException {
        System.out.println("FATAL ERROR Occured");
        exception.printStackTrace();
    }

}

Step 7: Run it

Below is the complete Main class:
com.websystique.xml.JaxbSchemaValidation

package com.websystique.xml;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.util.JAXBSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.joda.time.LocalDate;
import org.xml.sax.SAXException;

import com.websystique.xml.model.CustomValidationErrorHandler;
import com.websystique.xml.model.Student;
import com.websystique.xml.model.University;

public class JaxbSchemaValidation {

	private static final String XML_FILE = "education_centers.xml";

	public static void main(String[] args) throws JAXBException, SAXException,
			IOException {

		JaxbSchemaValidation schemaValidation = new JaxbSchemaValidation();

		List<Student> students = new ArrayList<Student>();

		Student s1 = new Student();
		s1.setId(1);
		s1.setFirstName("Alan");
		s1.setLastName("Turing");
		s1.setSection("Com");
		s1.setDob(new LocalDate(1956, 10, 1));
		students.add(s1);

		Student s2 = new Student();
		s2.setId(2);
		s2.setFirstName("Thomas");
		s2.setLastName("Edison");
		s2.setSection("Artio");
		s2.setDob(new LocalDate(1916, 3, 3));
		students.add(s2);

		Student s3 = new Student();
		s3.setId(3);
		s3.setFirstName("Linus");
		s3.setLastName("Torvald");
		s3.setSection("Computer Science");
		s3.setDob(new LocalDate(1958, 11, 4));
		students.add(s3);

		University university = new University();
		university.setName("Cambridge");
		university.setAddress("England");
		university.setStudents(students);

		SchemaFactory sf = SchemaFactory
				.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
		Schema schema = sf.newSchema(schemaValidation.getSchemaFile());

		// create JAXB context
		JAXBContext context = JAXBContext.newInstance(University.class);

		System.out.println("<!----------Generating the XML Output-------------->");
		// Instantiate marshaller via context
		Marshaller m = context.createMarshaller();
		m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
		m.setSchema(schema);

		// Write to System.out
		m.marshal(university, System.out);
		m.marshal(university, new File(XML_FILE));

		System.out.println("<!---------------Generating the Java objects from XML Input-------------->");
		// Instantiate Unmarshaller via context
		Unmarshaller um = context.createUnmarshaller();
		University unif = (University) um.unmarshal(new FileReader(XML_FILE));
		List<Student> studentsList = unif.getStudents();
		for (Student s : studentsList) {
			System.out.println("Student : " + s);
		}

		// JAXBSource source = new JAXBSource(context, university);
		// Validator validator = schema.newValidator();
		// validator.setErrorHandler(new CustomValidationErrorHandler());
		// validator.validate(source);
	}

	private File getSchemaFile() {
		// Get file from resources folder
		ClassLoader classLoader = getClass().getClassLoader();
		File schemaFile = new File(classLoader.getResource("edu.xsd").getFile());
		return schemaFile;
	}

}

Below is the generated output:

Exception in thread "main" javax.xml.bind.MarshalException
 - with linked exception:
[org.xml.sax.SAXParseException: cvc-minLength-valid: Value 'Com' with length = '3' is not facet-valid with respect to minLength '4' for type '#AnonType_sectionStudent'.]
	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:317)
	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243)
	at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75)
	at com.websystique.xml.JaxbSchemaValidation.main(JaxbSchemaValidation.java:81)
Caused by: org.xml.sax.SAXParseException: cvc-minLength-valid: Value 'Com' with length = '3' is not facet-valid with respect to minLength '4' for type '#AnonType_sectionStudent'.
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:195)

With correct input[constraint meets], we will see following output

<!----------Generating the XML Output-------------->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<University>
    <name>Cambridge</name>
    <address>England</address>
    <Students>
        <Student id="1">
            <firstName>Alan</firstName>
            <lastName>Turing</lastName>
            <birthDate>1956-10-01</birthDate>
            <section>Comp</section>
        </Student>
        <Student id="2">
            <firstName>Thomas</firstName>
            <lastName>Edison</lastName>
            <birthDate>1916-03-03</birthDate>
            <section>Artio</section>
        </Student>
        <Student id="3">
            <firstName>Linus</firstName>
            <lastName>Torvald</lastName>
            <birthDate>1958-11-04</birthDate>
            <section>Computer Science</section>
        </Student>
    </Students>
</University>
<!---------------Generating the Java objects from XML Input-------------->
Student : Student [id=1, firstName=Alan, lastName=Turing, dob=1956-10-01, section=Comp]
Student : Student [id=2, firstName=Thomas, lastName=Edison, dob=1916-03-03, section=Artio]
Student : Student [id=3, firstName=Linus, lastName=Torvald, dob=1958-11-04, section=Computer Science]

That’s it. In the next post , we will discuss about the code generation using XSD.

Download Source Code



References