JAXB Code Binding and Generation

We’ve said that our ultimate goal in this chapter is automated binding of XML to Java classes. Now we’ll discuss the standard Java API for XML Binding, JAXB. (This should not be confused with JAXP, the parser API.) JAXB is a standard extension that is bundled with Java 6 and later. With JAXB, the developer does not need to create any fragile parsing code. An XML schema or Java code can be used as the starting point for transforming XML to Java and back. (“Schema first” and “code first” are both supported.) With JAXB, you can either mark up your Java classes with simple annotations that map (bind) them to XML or start with an XML schema and generate plain Java classes (POJOs) with the necessary annotations included. You can even derive an XML schema from your Java classes to use as a starting point or contract with non-Java systems.

At runtime, JAXB can read an XML document and parse it into the model that you have defined or you can go the other way, populating your object model and then writing it out to XML. In both cases, JAXB can validate the data to make sure it matches a schema. This may sound like the DOM interface, but in this case we’re not using generic classes—we’re using our own model. In this section, we’ll reuse the class model that we created for the SAX example with our zooinventory.xml file. We’ll use the familiar Inventory, Animal, and FoodRecipe classes directly, but this time you’ll see that we’ll be more focused on the schema and names and less on the parsing machinery.

Annotating Our Model

JAXB gives us a great deal of flexibility in mapping our Java classes to XML elements and there are a lot of special cases. But if we accept most of the default behavior for our model, we can get started with very little work. Let’s start by taking our zoo inventory classes and adding the necessary annotations to allow JAXB to bind it to XML:

@XmlRootElement
public class Inventory {
       public List<Animal> animal = new ArrayList<>();
}

Well, that was easy! Yes, in fact as we hinted at the beginning of the chapter, adding just the @XmlRootElement annotation to the “top level” or root class of our model will yield nearly the same XML that we used before. To generate the XML, we’ll use the following test harness:

    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBException;
    import javax.xml.bind.Marshaller;
    
    public class TestJAXBMarshall
    {
        public static void main( String [] args ) throws JAXBException {
            Inventory inventory = new Inventory();
            FoodRecipe recipe = new FoodRecipe();
            recipe.name = "Gorilla Chow";
            recipe.ingredient.addAll( Arrays.asList( "leaves", "insects",
                "fruit" ) );
            Animal animal = new Animal( Animal.AnimalClass.mammal, "Song Fang", 
                "Giant Panda", "China", "Bamboo", "Friendly", 45.0, recipe );
            inventory.animal.add( animal );
            
            marshall( inventory );
        }
        
        public static void marshall( Object jaxbObject ) throws JAXBException {
            JAXBContext context = JAXBContext.newInstance(
                jaxbObject.getClass() );
            Marshaller marshaller = context.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
                Boolean.TRUE);
            marshaller.marshal(jaxbObject, System.out);
        }
    }

We’ve taken the liberty of adding some constructors to shorten the code for creating the model, but it doesn’t change the behavior here. It’s just the four lines of our marshall() method that actually use JAXB to write out the XML. We first create a JAXBContext, passing in the class type to be marshalled. We’ve made our marshall() method somewhat reusable by getting the class type from the object passed in. However, it’s sometimes necessary to pass in additional classes to the newInstance() method in order for JAXB to be aware of all of the bound classes that may be needed. In that case, we’d simpy pass more class types to the newInstance() method (it accepts a variable argument list with any number of arguments—of class types). We then create a Marshaller from the context and, for our purposes, set a flag indicating that we would like nice, human-readable output (the default output is one long line of XML). Finally, we tell the marshaller to send our object to System.out.

The output looks like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<inventory>
    <animal>
        <animalClass>mammal</animalClass>
        <name>Song Fang</name>
        <species>Giant Panda</species>
        <habitat>China</habitat>
        <food>Bamboo</food>
        <temperament>Friendly</temperament>
        <weight>45.0</weight>
    </animal>
    <animal>
        <animalClass>mammal</animalClass>
        <name>Cocoa</name>
        <species>Gorilla</species>
        <habitat>Ceneral Africa</habitat>
        <temperament>Know-it-all</temperament>
        <weight>45.0</weight>
        <foodRecipe>
            <name>Gorilla Chow</name>
            <ingredient>fruit</ingredient>
            <ingredient>shoots</ingredient>
            <ingredient>leaves</ingredient>
        </foodRecipe>
    </animal>
</inventory>

As we said, it’s almost identical to the XML we worked with earlier. Admittedly, we chose to create our XML using the same (common) conventions that JAXB uses, so it’s not entirely magic. The first thing to notice is that JAXB automatically mapped our class names to lowercase XML element names (e.g., class Animal to <animal>). If we had used JavaBeans-style getter methods instead of public fields, the same would be true; for example, a getSpecies() method would produce a default element name of species.

If we wanted to map our class names and property names to completely different XML names, we could easily accomplish that using the name attribute of the @XmlRootElement and @XmlElement annotations. For example, we can call our Animal “creature” and rename temperament to “personality” like so:

@XmlRootElement(name="creature")
public class Animal 
{
    ...
    @XmlElement(name="personality")
    public String temperament;

The real difference between our generated XML and our earlier sample is that our animalClass attribute is not acting like an attribute. By default, is has been mapped to an element, like the other properties of Animal. We can rectify that with another annotation, @XmlAttribute:

public class Animal 
{
    @XmlAttribute
    public AnimalClass animalClass;    
    ...

// Produces...

<inventory>
    <animal animalClass="mammal">
        <name>Song Fang</name>

Also note that JAXB has shown the food element in the first animal and the foodRecipe in the second. JAXB will ignore a field or property that is null (as is the case here) unless you specify that the property is “nillable” using @XmlElement(nillable=true). That behavior automatically supported the alternation between our two properties.

There are many additional annotations that provide support for mapping Java classes, fields, and properties to other features of XML. Table 24-6 attempts to provide a concise description of what each annotation is used for. Some of the usages get a little complex,so you may want to refer to the Javadoc for more details.

Table 24-6. JAXB Annotations

AnnotationDescription
@XmlAccessorOrderUsed on a package or class to set alphabetic ordering of marshalled fields and properties. (The default ordering is undefined.) See @XmlType to specify the ordering yourself. As a reminder: package-level annotations in Java are placed on a (lonely) package statement in a special file named package-info.java within the corresponding package structure. (See Annotations.)
@XmlAccessorTypeUsed on a package or class to specify whether fields and properties are marshalled by default. You can choose: only fields, only properties (getters/setters), none (only those annotated by the user), or all public fields and properties. See @XmlTransient to exclude items.
@XmlAnyAttributeDesignates a Java Map object to receive any unbound XML attribute name-value pairs for an entity (i.e., the Map will collect any leftover attributes for which no corresponding property or field can be found).
@XmlAnyElementDesignates a Java List or Array object to receive any unbound XML elements for an entity (i.e., the List will accumulate any leftover elements for which no corresponding property or field can be found).
@XmlAttachmentRefDesignates a java.activation.DataHandler object to handle an XML MIME attachment.
@XmlAttributeBinds a Java field or property to an XML attribute. The name attribute can be used to specify an XML attribute name that is different from the name of the field or property. Use the required attribute to specify whether the attribute is required.
@XmlElementBinds a a Java field or property to an XML element. The name attribute can be used to specify an XML element name different from the name of the field or property. Use the required attribute to specify whether the element is required.
@XmlElementsUsed on a Java collection to specify distinct element names for contained items based on their Java type. Holds a list of @XmlElement annotations with name and type attributes that explicitly map Java types in the collection to XML element names (e.g., in our example, inventory contains animal elements because our List property is named “animal”). If we chose to have subclasses of Animal in our inventory collection, we could map them to XML element names such as gorilla and lemur. See @XmlElementRef.
@XmlElementRefSimilar to @XmlElements, used to generate individualized names for Java types in a collection. However, instead of the names for each type being specified directly, they are determined at runtime by the individual types’ Java type bindings (e.g., in our example, inventory contains animal elements because our List is named “animal”). Using @XmlElementRef, we could subclass Animal and have our inventory contain elements like gorilla and lemur, with the names determined by @XmlRootElement annotations on the respective subclasses. See important class binding info in @XmlElementRefs.
@XmlElementRefsUsed on a Java collection to provide a list of @XmlElementRef annotations with type attributes that explicitly specify the Java types that may appear in the collection. The effect is the same as using a simple @XmlElementRef on the collection, but we actively tell JAXB the class names that have bindings. If not supplied in this way, we have to provide the full list of bound classes to the JAXBContextnewInstance() method in order for them to be recognized.
@XmlElementWrapperUsed on a Java collection to cause the sequence of XML elements to be wrapped in the specified element instead of appearing directly inline in the XML (e.g., our animal elements appear directly in inventory). Using this annotation, we could nest them all within a new animals element.
@XmlEnumBinds a Java Enum to XML and allows @XmlEnumValues annotations to be used to map the enum values for XML if required.
@XmlEnumValueBinds an individual Java Enum value to a string to be used in the XML (e.g., our mammal enum value could be mapped to “mammalia”).
@XmlIDSupports referential integrity by designating a Java property or field of a class as being the XML ID attribute (a unique key) for the XML element within the document.
@XmlIDREFSupports referential integrity by designating a Java property or field as an idref attribute pointing to an element with an @XmlID. The annotated property or field must contain an instance of a Java type containing an @XmlID annotation. When marshalled, the attribute name will be the property name and the value will be the contained XML ID value.
@XmlInlineBinaryDataBind a Java byte array to receive base64 binary data encoded in the XML.
@XmlListUsed on a Java collection to map items to a single simple content element with a whitespace-separated list of values instead of a series of elements.
@XmlMimeTypeUsed with a Java Image or Source type to specify a MIME type for XML base64-encoded binary data bound to it.
@XmlMixedBinds a Java object collection to XML “mixed content” (i.e., XML containing both text and element tags within it). Text will be added to the collection as String objects interleaved with the usual Java types representing the other elements.
@XmlRootElementBind a Java class to an XML element optionally provide a name. This is the minimum annotation required on your class to make it possible to marshal it to XML and back.
@XmlElementDeclUsed in binding XML schema elements to methods in Java object factories created in some code generation scenarios.
@XmlRegistryUsed with @XmlElementDecl in designating Java object factories used in some code generation scenarios.
@XmlSchemaBinds a Java package to a default XML namespace.
@XmlNsUsed with @XmlSchema to bind a Java package to one or more XML namespace prefixes.
@XmlSchemaTypeUsed on a Java property, field, or package. Specifies a Java type to be used for a standard XML schema built-in types, such as date or a numeric type.
@XmlSchemaTypesUsed on a Java package. Holds a list of @XmlSchemaType annotations mapping Java types to built-in XML schema types.
@XmlTransientDesignates that a Java property or field should not be marshaled to the XML. This can be used in conjunction with defaults that marshal all properties or fields to exclude individual items. See @XmlAccessorType.
@XmlTypeBinds a Java class to an XML schema type. Additionally, the propOrder attribute may be used to explicitly list the order in which elements are marshalled to XML.
@XmlValueDesignates that a Java property or field contains the “simple” XML content for the Java type; that is, instead of marshalling the class as an XML element containing a nested element for the property, the value of the annotated property will appear directly as the content. The Java type may have only one property designated as @XmlValue.

Unmarshalling from XML

Creating our object model from XML just requires a few lines to create an Unmarshaller from our JAXBContext and a cast to the Java type of our root element:

JAXBContext context = JAXBContext.newInstance( Inventory.class );
Unmarshaller unmarshaller = context.createUnmarshaller();
Inventory inventory = (Inventory)unmarshaller.unmarshal(
    new File("zooinventory.xml") );

The Unmarshaller class has a setValidating() method like the SAXParser, but it is deprecated. Instead, we could use the setSchema() method to set an XML Schema representation if we want validation as part of the parsing process. Alternately, we could just validate the schema separately. See XML Schema.

Generating a Java Model from an XML Schema

If you are starting with an XML Schema (xsd file), you can generate annotated Java classes from the schema using the JAXB xjc command-line tool that comes with the JDK.

xjc zooinventory.xsd

// Output
parsing a schema...
compiling a schema...
generated/Animal.java
generated/FoodRecipe.java
generated/Inventory.java
generated/ObjectFactory.java

By default, the output is placed in the default package in a directory named generated. You can control the package name with the -p switch and the directory with -d. See the xjc documentation for more options.

Studying the generated classes will give you some hints as to how many annotations are used, although xjc is a little more verbose than it has to be. Also note that xjc produces a class called ObjectFactory that contains factory methods for each type, such as createInventory() and createAnimal(). If you look at these methods, you’ll see that they really just call new on the plain Java objects and they seem superfluous. The ObjectFactory is mainly there for legacy reasons. In ealier versions of JAXB, before annotations, the generated classes were not as simple to construct. Additionally, the ObjectFactory contains a helper method to create a JAXBElement type, which may be useful in special situations. For the most part, you can ignore these.

Generating an XML Schema from a Java Model

You can also generate an XML Schema directly from your annotated Java classes using the JAXB XML Schema binding generator: schemagen. The schemagen command-line tool comes with the JDK. It can generate a schema starting with Java source or class files. Use the -classpath argument to specify the location of the classes or source files and then provide the name of the root class in your hierarchy:

schemagen -classpath . Inventory

Having worked our way through the options for bridging XML to Java, we’ll now turn our attention to transformations on XML itself with XSL, the styling language for XML.

Get Learning Java, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.