JAXB without a schema

~ Duck Talk ~

Creative Commons License photo credit: ViaMoi

Working on a new project, I had a few chats to people and realized that many developers assume that to work with JAXB you need to have an XML schema.
This is not true. If you have the schema, go ahead and use it, but if you only have the XML files you want to parse, JAXB is perfectly capable of unmarshalling your XML into objects without the schema and the binding.
The only problem is – you’ll have to code the binding classes yourself. However – this isn’t always a bad thing.

Coding the classes yourself lets you take some good shortcuts, disregard elements you are not interested in, and control behavior better.
I would like to add that you usually end up manually editing the generated classes anyway – so if your XML files are relatively simple – might as well do it the old fashioned way.

An example

Let’s consider the following XML document:

<?xml version="1.0"?>
<user>
    <name>Duck Ranger</name>
    <accountNumber>45</accountNumber>
    <username>duckr</username>
    <COMMAND>list</COMMAND>
    <superUser>false</superUser>
    <PROGRAMS>
        <program>
            <executable>processor.exe</executable>
            <directory>/products/processor</directory>
        </program>
        <program>
            <executable>preprocessor.exe</executable>
            <directory>/products/preprocessor</directory>
        </program>
    </PROGRAMS>
    <lastLogin>21-10-2010</lastLogin>
</user>

A very simple XML file. right?
To parse it using JAXB, we need to tell the unmarshaller which classes are present. The rule of thumb is simple: If an element contains other elements – it is a class. However, if it is a leaf-element – it will be a member of the containing class.

Following this rule, it is obvious that User is a class. It contains everything else. However, name is not a class – it will be a member of the User class.

So how will it look like in code?

Program.java

The inner-most class describes the program element.

package com.duckranger.jaxb;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

//The XMLRootElement annotation tells JAXB what is the 
//element local name it needs to look for.
@XmlRootElement(name="program")
public class Program {
    
    private String executable;
    private String directory;
     
    //The adapter tells JAXB to collapse white space. This is usually 
    //quite good to use for Strings.
    //The XmlElement annotation tells JAXB what's the 
    //actual name of the element inside <program>
    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
    @XmlElement(name="executable")
    public String getExecutable() {
        return executable;
    }

    public void setExecutable(String executable) {
        this.executable = executable;
    }
	
    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
    @XmlElement(name="directory")
    public String getDirectory() {
        return directory;
    }

    public void setDirectory(String directory) {
        this.directory = directory;
    }

    @Override
    public String toString() {
        return "Program ["
            + (executable != null ? "executable=" + executable + ", " : "")
            + (directory != null ? "directory=" + directory : "") + "]";
    }

}

There’s really not much to this class. It is the inner-most level we are getting to inside the sample XML document. Let’s move on to the next class:

Programs.java

Programs is just a container class for Program elements.

package com.duckranger.jaxb;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

//Note that the element is all up-case. The Java class does not
//have to have exactly the same name as the element. It is
//enough that you create the right mapping. JAXB automatic
//bindings will not do that for you.
@XmlRootElement(name="PROGRAMS")
public class Programs {
	
    private List<Program> programs;

    @XmlElement(name = "program")
    public List<Program> getPrograms() {
        if (null==programs) {
            programs = new ArrayList<Program>();
        }
        return programs;
    }

    public void setPrograms(List<Program> programs) {
        this.programs = programs;
    }

    @Override
    public String toString() {
        return "Programs [" + (programs != null ? "programs=" + programs : "") + "]";
    }

}

The Programs.java class is a container – it holds a list of Program objects. This is not absolutely necessary (You could have the User class hold the list of Program objects directly) – but it is clearer this way.
Note that in the getter annotation I use the name “program”. This tells JAXB that the list used here maps to elements
with the name “program”.

User.java

The User class contains the attributes that are leafs inside the xml file. It also contains a reference to a Programs object, which provides a list of programs.

package com.duckranger.jaxb;

import java.util.Date;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement(name="user")
public class User {

    private String name;
    private Integer accountNumber;
    private String userName;
    private String command;
    private Boolean superUser;
    private Date lastLogin;
	
    private Programs programs;

    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
    @XmlElement(name="name")
    public String getName() {
        return name;
    }

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

    @XmlElement(name="accountNumber")
    public Integer getAccountNumber() {
        return accountNumber;
    }

    public void setAccountNumber(Integer accountNumber) {
        this.accountNumber = accountNumber;
    }

    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
    @XmlElement(name="username")
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    //As you can see, the member name does not have to correspond
    //to the actual element name, as long as you have the mapping
    //sorted.
    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
    @XmlElement(name="COMMAND")
    public String getCommand() {
        return command;
    }

    public void setCommand(String command) {
        this.command = command;
    }

    @XmlElement(name="superUser")
    public Boolean getSuperUser() {
        return superUser;
    }

    public void setSuperUser(Boolean superUser) {
        this.superUser = superUser;
    }

    @XmlElement(name="lastLogin")
    public Date getLastLogin() {
        return lastLogin;
    }

    public void setLastLogin(Date lastLogin) {
        this.lastLogin = lastLogin;
    }

    @XmlElement(name="PROGRAMS")
    public Programs getPrograms() {
        return programs;
    }

    public void setPrograms(Programs programs) {
        this.programs = programs;
    }

    @Override
    public String toString() {
        return "User ["
            + (name != null ? "name=" + name + ", " : "")
            + (accountNumber != null ? "accountNumber=" + accountNumber
            + ", " : "")
            + (userName != null ? "userName=" + userName + ", " : "")
            + (command != null ? "command=" + command + ", " : "")
            + (superUser != null ? "superUser=" + superUser + ", " : "")
            + (lastLogin != null ? "lastLogin=" + lastLogin + ", " : "")
            + (programs != null ? "programs=" + programs : "") + "]";
    }
	
}

With all the classes set, all that’s left is to create a driver – something to unmarshall the XML file:

Unmarshal.java

package com.duckranger.jaxb;

import java.io.FileReader;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;

public class Unmarshal {

    public static void main(String args[]) throws Exception {

        JAXBContext context = JAXBContext.newInstance(User.class); //1
        Unmarshaller unmarshaller = context.createUnmarshaller();
        User user = (User)unmarshaller.unmarshal(new FileReader("./duckranger.xml")); //2
        System.out.println(user); //3
        
    }    
}

In the simple code above, there are 3 points to notice:

  1. The first thing you need to do is create a JAXB context. The simplest way to create it is using the base class for the XML file (as shown above). You can also create it more generic by pointing it to the package.
  2. I then create a User object from the file.
  3. Using the sophisticated toString() methods on all classes shown above, the final line in the main method will print the following (for the XML file provided) :
User [name=Duck Ranger, accountNumber=45, userName=duckr, command=list, superUser=false, programs=Programs [programs=[Program [executable=preprocessor.exe, directory=/products/preprocessor], Program [executable=processor.exe, directory=/products/processor]]]]

14 thoughts on “JAXB without a schema

  1. Blaise Doughan

    Hello,

    Very nice article. Since JAXB is configuration by exception (only annotate where you want the mapping to be different from the default behaviour, you can simplify your mappings even further.

    For example:


    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
    @XmlElement(name="directory")
    public String getDirectory() {
    return directory;
    }

    Could also be coded as the following since by default all properties are considered to be @XmlElement by default, and the default XML name for this property would be “directory”.


    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
    public String getDirectory() {
    return directory;
    }

    -Blaise

  2. Agus

    Nice article.

    You may drop the Programs class (it’s of no use unless it has properties) thanks to the XmlElementWrapper annotation.

    In class User:

    private List programs = new ArrayList();

    @XmlElementWrapper(name = “PROGRAMS”)
    @XmlElement(name = “program”)
    public List getPrograms() {
    return programs;
    }

  3. Jitendra

    I have following XML.




    1
    xyz


    Is there a way to create only two classes, one for root(Root.java) and one for child(Child.java), but not for level1.

    I tried to skip level1 in Root.java by annotating child as @XmlElementWrapper(name = “level1”) but it throws exception ‘@XmlElementWrapper is only allowed on a collection property ‘.

  4. Jitendra

    My Root class looks like this.

    @XmlRootElement(name="root")
    public Class Root {
    @XmlElementWrapper(name = "level1")
    @XmlElement(name="child")
    private Child child;

    public Child getChild(){
    return child;
    }
    }

  5. Ajaya Sahoo

    Hi,

    Nice to know that I can use JAXB without xsd.

    My requirement is to add attribute to one of the elements. How should I achieve that?

    Thanks,
    Ajaya

  6. Ajaya Sahoo

    When I run the program, Unmarshal gives me compilation error as JAXBContext.newInstance(..) expects String rather than Class.

    Also unmarshaller.unmarshal(new FileReader(..)) gives error as it expects File not file Reader.

    I have updated the code as following and the following error comes up as follows:

    JAXBContext context = JAXBContext.newInstance(“.”); //1
    Unmarshaller unmarshaller = context.createUnmarshaller();
    User user = (User)unmarshaller.unmarshal(new File(“./duckranger.xml”)); //2
    System.out.println(user); //3

    Exception in thread “main” javax.xml.bind.JAXBException: Unable to locate jaxb.properties for package .
    at javax.xml.bind.ContextFinder.searchcontextPath(ContextFinder.java:205)
    at javax.xml.bind.ContextFinder.find(ContextFinder.java:149)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:281)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:238)
    at com.duckranger.jaxb.Unmarshal.main(Unmarshal.java:19)

  7. Pingback: Technology And Software » JAXB without a schema | duckranger.com

  8. Pingback: XML & MQ best solution | PHP Developer Resource

  9. seshu

    This really helped me. But i have one more query regarding this. In my application in order to use the @XmlJavaTypeAdapter(CollapsedStringAdapter.class) based on the input data. I mean i have two modes of input string in which one i need to trim using @XmlJavaTypeAdapter(CollapsedStringAdapter.class) and another should not. For this i used like
    @XmlAttribute(name = “AccountNumber”, required = true)
    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
    protected String accountNumber;
    @XmlAttribute(name = “AccountNumber”, required = true)
    protected String accountNumberHashed;

    but i not getting the value for accountNumberHashed. Please help me for the same.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>