Wednesday, December 29, 2010

JAXB : validation against XML schema at marshalling/unmarshalling

1 Introduction

One of the most commonly made mistakes when using JAXB to bind XML to Java objects, is to think that marshalling and unmarshalling operations will automatically validate the processed data values against the XML schema that has been given as input to the XJC program to generate corresponding Java classes, but as a matter of fact they don't.

Practically, lots of programmer won't even notice that aspect because they usually don't defined any constraint in their XML schema (multiplicy, pattern, min/max value, min/max length, ...), or because the values they handle remain valid throughout the different steps of their program.

So here's how I turn validations on when using JAXB...

2 Tools
  • JDK 1.6.0_14
  • JAXB 2.1.10 in JDK 6 
3 XML schema


<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/users" xmlns:tns="http://www.example.org/users" elementFormDefault="qualified">

    <complexType name="User">
        <sequence>
            <element name="login" type="string" maxOccurs="1"
                minOccurs="1">
            </element>
            <element name="password" maxOccurs="1" minOccurs="1">
                <simpleType>
                    <restriction base="string">
                        <minLength value="6"></minLength>
                        <pattern value=".*\d+.+"></pattern>
                    </restriction>
                </simpleType>
            </element>
            <element name="subscriptionDate" type="date" maxOccurs="1" minOccurs="1"></element>
        </sequence>
    </complexType>

    <complexType name="UserList">
        <sequence>
            <element name="users" type="tns:User" maxOccurs="unbounded" minOccurs="0"></element>
        </sequence>
    </complexType>

    <element name="UserList" type="tns:UserList"></element>
</schema>


As you can see, I've specified several constraints :
  • a user must have a login, a password and a subscription date
  • the password must have at least 8 characters and at least 1 digit

4 Validation when marshalling

Here's some code to illustrate how to turn validation on :

public static void main(String[] args) {
        try {
            JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);
            Marshaller msh = context.createMarshaller();
            // setting a schema on the marshaller instance to activate validation against given XML schema
            msh.setSchema(SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema").newSchema(new File("xsd/users.xsd")));
            // specifies that the XML output must be indented
            msh.setProperty("jaxb.formatted.output", Boolean.TRUE);
           
            // creating a list of user
            UserList userList = new UserList();
           
            // creating an user
            User user = new User();
            user.setLogin("manager");
            user.setPassword("password01");
            user.setSubscriptionDate(DatatypeFactory.newInstance().newXMLGregorianCalendar(new GregorianCalendar(2010, 11, 29)));
           
            // adding the user to the list
            userList.getUsers().add(user);
            JAXBElement<UserList> element = new ObjectFactory().createUserList(userList);
           
            // marshalling the list to XML
            msh.marshal(element, new File("out/users.xml"));
        } catch (Exception e) {
            e.printStackTrace();
        }   
    }

Setting a schema on a marshaller/unmarshaller turns validation on : 

...
Marshaller msh  = context.createMarshaller();
msh.setSchema(SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema").newSchema(new File("xsd/users.xsd")));
...

The highlighted code lines specifies which schema should be used to validate against when marshalling.

Validation for unmarshalling operation is activated the same way : in the above highlighted code line, replace the marshaller instance (msh) by an unmarshaller instance and the trick is done

5 Example of valid and invalid marshalling operations

Based on the XML schema from step 3, trying to marshall a user list with a user whose password is "nopassword" will yield the following exception : 


Marshalling a user without specifying any login will yield the following exception : 


On the other hand, here's an example of valid XML output

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<UserList xmlns="http://www.example.org/users">
    <users>
        <login>manager</login>
        <password>password0</password>
        <subscriptionDate>2010-12-29+01:00</subscriptionDate>
    </users>
</UserList>

6 Source code

For those who aren't that familiar yet with JABX basic functionalities, feel free to leave a comment with your email address and I'll send you the project I used for this post.