15 September, 2011

Configure Maven to generate classes from XML Schema using JAXB

In my previous post I showed how to create RESTful services using Spring Framework. For representation of resources in XML I used JAXB and I followed the bottom-up approach (I wrote the Java classes and I let to generate XML/XSD from Java classes). In this post I will demonstrate how you can generate the same Java classes (User and UserList) from XML Schema (XSD) during Maven build, therefore using a top-down approach. For generation of Java classes from XML Schema during Maven build I will use Java.net Maven 2 JAXB 2 Plugin.

Along with the default generation of Java classes from XML Schema using xjc tool, I will add the following customizations:

  • Making all the generated classes to implement Serializable (useful when you want to use classes with some remote services: RMI, EJB, etc).
  • Use java.util.Calendar instead of javax.xml.datatype.XMLGregorianCalendar for fields generated from elements of type xs:dateTime.
  • Generated classes should also have a generated toString() method.
  • Classes have to be annotated with XmlRootElement (required by Spring Framework when classes are used to represent state in RESTful services).
  • You can further enhance and customize the generation of Java classes using various plugins.

Project sources are available for download. So let’s start, step by step:

  1. Create Maven project. Below you can see the POM:
    <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>org.zmeu</groupId>
        <artifactId>zmeu-blog-maven-jaxb</artifactId>
        <version>1.0-SNAPSHOT</version>
        <name>ZMEU Blog Maven JAXB</name>
        <build>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.6</source>
                        <target>1.6</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.jvnet.jaxb2.maven2</groupId>
                    <artifactId>maven-jaxb2-plugin</artifactId>
                    <version>0.8.0</version>
                    <configuration>
                        <schemaDirectory>src/main/resources/schema</schemaDirectory>
                        <bindingDirectory>src/main/resources/schema</bindingDirectory>
                        <generatePackage>org.zmeu.blog.jaxb</generatePackage>
                        <strict>false</strict>
                        <extension>true</extension>
                        <plugins>
                            <plugin>
                                <groupId>org.jvnet.jaxb2_commons</groupId>
                                <artifactId>jaxb2-basics</artifactId>
                                <version>0.6.2</version>
                            </plugin>
                            <plugin>
                                <groupId>org.jvnet.jaxb2_commons</groupId>
                                <artifactId>jaxb2-basics-annotate</artifactId>
                                <version>0.6.2</version>
                            </plugin>
                        </plugins>
                        <args>
                            <arg>-Xannotate</arg>
                            <arg>-XtoString</arg>
                        </args>
                    </configuration>
                    <executions>
                        <execution>
                            <id>generate</id>
                            <goals>
                                <goal>generate</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
        <dependencies>
            <dependency>
                <groupId>org.jvnet.jaxb2_commons</groupId>
                <artifactId>jaxb2-basics-runtime</artifactId>
                <version>0.6.2</version>
            </dependency>
        </dependencies>
    </project>
    
  2. Write XML Schema (schema.xsd):
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    
        <xs:element name="user" type="user" />
        <xs:element name="userList" type="userList" />
    
        <xs:complexType name="user">
            <xs:all>
                <xs:element name="id" type="xs:long" minOccurs="0" />
                <xs:element name="name" type="xs:string" />
                <xs:element name="registrationDate" type="xs:dateTime" />
            </xs:all>
        </xs:complexType>
    
        <xs:complexType name="userList">
            <xs:sequence>
                <xs:element name="user" type="user" minOccurs="0" maxOccurs="unbounded" />
            </xs:sequence>
        </xs:complexType>
        
    </xs:schema>
    
  3. Customize JAXB Bindings (binding.xjb):
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:annox="http://annox.dev.java.net" 
        xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
        version="2.1">
        <jaxb:globalBindings>
            <!-- Use java.util.Calendar instead of javax.xml.datatype.XMLGregorianCalendar for xs:dateTime -->
            <jaxb:javaType name="java.util.Calendar" xmlType="xs:dateTime"
                    parseMethod="javax.xml.bind.DatatypeConverter.parseDateTime" 
                    printMethod="javax.xml.bind.DatatypeConverter.printDateTime" />
    
            <!-- Force all classes implements Serializable -->
            <xjc:serializable uid="1" />
        </jaxb:globalBindings>
    
        <!-- Annotate the following classes with XmlRootElement -->
        <jaxb:bindings schemaLocation="schema.xsd" node="/xs:schema">
            <jaxb:bindings node="xs:complexType[@name='user']">
                <annox:annotate>
                    <annox:annotate annox:class="javax.xml.bind.annotation.XmlRootElement" name="user" />
                </annox:annotate>
            </jaxb:bindings>
            <jaxb:bindings node="xs:complexType[@name='userList']">
                <annox:annotate>
                    <annox:annotate annox:class="javax.xml.bind.annotation.XmlRootElement" name="userList" />
                </annox:annotate>
            </jaxb:bindings>
        </jaxb:bindings>
    </jaxb:bindings>
    
  4. Run the build using mvn clean install command. Build must be successful. Generated classes will be located in target/generated-sources/xjc directory. Below is a snippet from generated User class:
    
    .....
    
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "user", propOrder = {})
    @XmlRootElement(name = "user")
    public class User implements Serializable, ToString {
        private final static long serialVersionUID = 1L;
        
        protected Long id;
        
        @XmlElement(required = true)
        protected String name;
        
        @XmlElement(required = true, type = String.class)
        @XmlJavaTypeAdapter(Adapter1 .class)
        @XmlSchemaType(name = "dateTime")
        protected Calendar registrationDate;
    
        .....
    
    }
    
  5. You are done!

Update (01-Jun-2013): In case you have several schema files and you wish to generate java classes in different packages then you have the option to specify the package inside JAXB binding file (*.xjb). For this you have to:

  • Remove generatePackage element from maven-jaxb2-plugin configuration. In our case remove the line: <generatePackage>org.zmeu.blog.jaxb</generatePackage>
  • Add the desired package name for every schema file inside JAXB binding file (*.xjb) as follows (see the highlighted section):
    <jaxb:bindings schemaLocation="schema.xsd" node="/xs:schema">
        <jaxb:schemaBindings>
            <jaxb:package name="org.zmeu.blog.jaxb.user" />
        </jaxb:schemaBindings>
    
        <jaxb:bindings node="xs:complexType[@name='user']">
            <annox:annotate>
                <annox:annotate annox:class="javax.xml.bind.annotation.XmlRootElement" name="user" />
            </annox:annotate>
        </jaxb:bindings>
        <jaxb:bindings node="xs:complexType[@name='userList']">
            <annox:annotate>
                <annox:annotate annox:class="javax.xml.bind.annotation.XmlRootElement" name="userList" />
            </annox:annotate>
        </jaxb:bindings>
    </jaxb:bindings>
    

23 comments:

  1. KB at 34.2 KB/sec)
    INFO] ------------------------------------------------------------------------
    INFO] BUILD FAILURE
    INFO] ------------------------------------------------------------------------
    INFO] Total time: 1:03.273s
    INFO] Finished at: Wed Aug 01 10:16:02 BST 2012
    INFO] Final Memory: 3M/7M
    INFO] ------------------------------------------------------------------------
    ERROR] Failed to execute goal org.jvnet.jaxb2.maven2:maven-jaxb2-plugin:0.8.0:generate (generate) on project zmeu-blog-maven-jaxb: Execution generate of goal o
    g.jvnet.jaxb2.maven2:maven-jaxb2-plugin:0.8.0:generate failed: An API incompatibility was encountered while executing org.jvnet.jaxb2.maven2:maven-jaxb2-plugin
    0.8.0:generate: java.lang.LinkageError: JAXB 2.1 API is being loaded from the bootstrap classloader, but this RI (from jar:file:/C:/Users/akisanyas/.m2/reposit
    ry/com/sun/xml/bind/jaxb-impl/2.2.4-1/jaxb-impl-2.2.4-1.jar!/com/sun/xml/bind/v2/model/impl/ModelBuilder.class) needs 2.2 API. Use the endorsed directory mecha
    ism to place jaxb-api.jar in the bootstrap classloader. (See http://java.sun.com/j2se/1.6.0/docs/guide/standards/)
    ERROR] -----------------------------------------------------
    ERROR] realm = plugin>org.jvnet.jaxb2.maven2:maven-jaxb2-plugin:0.8.0
    ERROR] strategy = org.codehaus.plexus.classworlds.strategy.SelfFirstStrategy
    ERROR] urls[0] = file:/C:/Users/akisanyas/.m2/repository/org/jvnet/jaxb2/maven2/maven-jaxb2-plugin/0.8.0/maven-jaxb2-plugin-0.8.0.jar
    ERROR] urls[1] = file:/C:/Users/akisanyas/.m2/repository/org/jvnet/jaxb2/maven2/maven-jaxb2-plugin-core/0.8.0/maven-jaxb2-plugin-core-0.8.0.jar
    ERROR] urls[2] = file:/C:/Users/akisanyas/.m2/repository/com/sun/org/apache/xml/internal/resolver/20050927/resolver-20050927.jar
    ERROR] urls[3] = file:/C:/Users/akisanyas/.m2/repository/backport-util-concurrent/backport-util-concurrent/3.1/backport-util-concurrent-3.1.jar
    ERROR] urls[4] = file:/C:/Users/akisanyas/.m2/repository/org/codehaus/plexus/plexus-interpolation/1.11/plexus-interpolation-1.11.jar
    ERROR] urls[5] = file:/C:/Users/akisanyas/.m2/repository/org/codehaus/plexus/plexus-utils/1.5.15/plexus-utils-1.5.15.jar
    ERROR] urls[6] = file:/C:/Users/akisanyas/.m2/repository/junit/junit/4.8.1/junit-4.8.1.jar
    ERROR] urls[7] = file:/C:/Users/akisanyas/.m2/repository/org/jfrog/maven/annomojo/maven-plugin-anno/1.3.1/maven-plugin-anno-1.3.1.jar
    ERROR] urls[8] = file:/C:/Users/akisanyas/.m2/repository/org/jvnet/jaxb2/maven2/maven-jaxb22-plugin/0.8.0/maven-jaxb22-plugin-0.8.0.jar
    ERROR] urls[9] = file:/C:/Users/akisanyas/.m2/repository/com/sun/xml/bind/jaxb-impl/2.2.4-1/jaxb-impl-2.2.4-1.jar
    ERROR] urls[10] = file:/C:/Users/akisanyas/.m2/repository/javax/xml/bind/jaxb-api/2.2.3/jaxb-api-2.2.3.jar
    ERROR] urls[11] = file:/C:/Users/akisanyas/.m2/repository/javax/xml/stream/stax-api/1.0-2/stax-api-1.0-2.jar
    ERROR] urls[12] = file:/C:/Users/akisanyas/.m2/repository/javax/activation/activation/1.1/activation-1.1.jar
    ERROR] urls[13] = file:/C:/Users/akisanyas/.m2/repository/com/sun/xml/bind/jaxb-xjc/2.2.4-1/jaxb-xjc-2.2.4-1.jar
    ERROR] Number of foreign imports: 1
    ERROR] import: Entry[import from realm ClassRealm[maven.api, parent: null]]
    ERROR]
    ERROR] -----------------------------------------------------
    ERROR] -> [Help 1]
    ERROR]
    ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
    ERROR] Re-run Maven using the -X switch to enable full debug logging.
    ERROR]
    ERROR] For more information about the errors and possible solutions, please read the following articles:
    ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/PluginContainerException

    ReplyDelete
  2. I am not sure what to do about this please advise.

    ReplyDelete
    Replies
    1. It seems that you have a conflict with JAXB dependencies defined in your project. I see that you are using jaxb-api:2.2.3 and jaxb-impl:2.2.4-1.

      The problem is that Java 6 includes JAXB 2.1, so there is no need to include it at all in your project. Here http://jaxb.java.net/guide/Which_JAXB_RI_is_included_in_which_JDK_.html you can see which JAXB RI is included in which JDK.

      In case you still require to use a specific version of JAXB and you encounter such errors then you may try to put the jaxb-api.jar that you're trying to use into JDK_HOME/jre/lib/endorsed. See http://blog.spaceprogram.com/2007/05/how-to-fix-linkageerror-when-using-jaxb.html for more details.

      Delete
  3. Great post - not having the @XmlRootElement annotation was causing me a headache with Spring (using the RestTemplate requires the annotation to be attached).

    Thanks very much

    ReplyDelete
  4. Great tutorial, thanks.

    Next URL will help to prevent some doubts regarding where we have to place *.xjb file:

    https://java.net/projects/maven-jaxb2-plugin/pages/Home

    ReplyDelete
    Replies
    1. I mentioned in the post itself that you can download the entire maven project used in this blog post. There you may see full pom.xml and where all the files resides.

      In your case the location of *.xsd and *.xjb files are defined in maven-jaxb2-plugin plugin configuration in pom.xm as follows:

      <schemaDirectory>src/main/resources/schema</schemaDirectory>
      <bindingDirectory>src/main/resources/schema</bindingDirectory>

      Delete
  5. Good one!! I am using a similar setting for date conversion to java.util.Calendar. The classes are auto generated by maven. But my problem is whenever I give a blank date it creates unmarshalling error.(like ) it gives Unmarshalling Error: java.lang.IllegalArgumentException: . How can I customize the generated Adapter1.class that is generated (public class Adapter1
    extends XmlAdapter) to avoid the illegal argument exception

    ReplyDelete
    Replies
    1. You may try to specify your own custom converters. Change
      <jaxb:javaType name="java.util.Calendar" xmlType="xs:dateTime"
                      parseMethod="javax.xml.bind.DatatypeConverter.parseDateTime"
                      printMethod="javax.xml.bind.DatatypeConverter.printDateTime" />

      to use your own parseMethod and printMethod.

      Delete
    2. This comment has been removed by the author.

      Delete
  6. I have a schema where the element and the attribute share the same identifier in a particular complex type. I want to rename the attributes identifier in the generated pojo ,I understand we can achieve t by using the bindings individually,
    Is there a way I can do it by replacing recursively in all the complex type wherever it conflicts Or Rename a particular attribute wherever it occurs in an Schema without mentioning the complex type.






    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Hi I wanted to know can I write the below code

    annox:annotate
    annox:annotate annox:class="javax.xml.bind.annotation.XmlRootElement"
    name="{@id}"
    annox:annotate


    in such a way that when it generated class suppose there are two User and Userlist then @XmlRootElement will pick up the respective object name like for user @XmlRootElement(name='user') and for userlist @XmlRootElement(name='userList') respectively , is there any way to write the standard annotation .Please help I googled I alot very new to jaxb , dont find a solution.

    Thanks in advance

    ReplyDelete
    Replies
    1. Code was not visible while posting so I removed the angular tags sorry about that

      Delete
  9. Is there a way to put prefix to the generated package name?

    ReplyDelete
    Replies
    1. What do you mean? You specify the package either globally in your pom.xml using generatePackage element or separately for every xsd in your xjb files using jaxb:schemaBindings element. Both cases are described above.

      Delete
  10. Hi
    Thanks for the tutorial.
    I have a big set of XSDs built over a period of many years, which take about 2 hours to compile.
    Is there a way to add the previous version of the jar to the classpath and build only the changed XSDs with each build?
    Thanks
    sanath.

    ReplyDelete
    Replies
    1. Please note that these files are not independent and the dependencies are scattered in all the files.

      Delete
    2. Unless you do a clean build, the plugin will generate the classes only for the schemas that has been changed. Check the plugin options for more details: http://confluence.highsource.org/display/MJIIP/User+Guide#UserGuide-Controllingtheoutput

      Delete
  11. If we generate java classes from XSD using JAXB by default it's generating java class with protected members. But i want it should be private. Any idea? How can we modify the default behavior?

    ReplyDelete
  12. Hi am trying to write common methods tostring using custom strategy could you help on that... The custom class is not referring ... Plz some example..

    ReplyDelete
    Replies
    1. You can specify your custom strategy in the maven-jaxb2-plugin as following:
      <args>
      <arg>-Xannotate</arg>
      <arg>-XtoString</arg>
      <arg>-XtoString-toStringStrategyClass=com.mypackage.MyCustomJAXBToStringStrategy</arg>
      </args>

      Here your custom strategy class has to extend org.jvnet.jaxb2_commons.lang.JAXBToStringStrategy

      Delete