Categories: Jaxb

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
Share
Published by

Recent Posts

Spring Boot + AngularJS + Spring Data + JPA CRUD App Example

In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring Data, JPA/Hibernate and MySQL,…

7 years ago

Spring Boot Rest API Example

Spring Boot complements Spring REST support by providing default dependencies/converters out of the box. Writing RESTful services in Spring Boot…

7 years ago

Spring Boot WAR deployment example

Being able to start the application as standalone jar is great, but sometimes it might not be possible to run…

7 years ago

Spring Boot Introduction + hello world example

Spring framework has taken the software development industry by storm. Dependency Injection, rock solid MVC framework, Transaction management, messaging support,…

7 years ago

Secure Spring REST API using OAuth2

Let's secure our Spring REST API using OAuth2 this time, a simple guide showing what is required to secure a…

8 years ago

AngularJS+Spring Security using Basic Authentication

This post shows how an AngularJS application can consume a REST API which is secured with Basic authentication using Spring…

8 years ago