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 Calendar instead of 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>
    

29 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
  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. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. Hi,

    I am using xjc(2.2.4-2) with jdk1.7.0_79. after generating class files from XSD, i can see some extra strings are getting added to all class files. like public JAXBElement getActor() {
    return actor;
    }

    I tried with binding.xjb. still no luck. could you please check this and share the proper.xjb files for removing .

    Thanks,

    ReplyDelete
  13. I’m looking forward for your next post, I’ll try to get the hang of it!

    출장안마

    ReplyDelete
  14. I’m curious to find out what blog system you have been working with? 타이마사지

    ReplyDelete
  15. Hello, I am one of the most impressed people in your article. 슬롯사이트 If possible, please visit my website as well. Thank you.


    ReplyDelete
  16. Dachshunds are bred and shown in two sizes: Standard and Miniature. https://www.cutespupsforsale.com/ Standard Dachshunds of all varieties (Smooth, Wirehair, and Longhair) usually weigh between 16 and 32 pounds. Miniature Dachshunds of all varieties weigh 11 pounds and under at teacup poodle for sale maturity. Dachshunds that weigh between 11 and 16 pounds are called Tweenies. Some people who breed exceptionally small Dachshunds advertise them as Toy Dachshunds, but this is purely a poodles for sale marketing term, not a recognized designation. He's bred for perseverance, which is another way of saying that he can be stubborn. Dachshunds have a reputation for being dachshund puppies sale entertaining and fearless, but what they want most is to cuddle with their people. Longhairs are calm and quiet, and Smooths have dachshund for sale a personality that lies somewhere in between. https://Greenlandpuppies.com Some Mini Dachshunds can be nervous or shy, but this isn't correct for the breed. Avoid puppies that show these characteristics.Like every dog, Dachshunds need early socialization-exposure to many different people, dachshund puppies for sale near me sights, sounds, and experiences-when they're young. Socialization helps ensure that your Dachshund puppy grows up to be a well-rounded dog. .

    ReplyDelete
  17. very nice article. thank you for sharing this post. who want to learn online courses. visit:
    Python Online Training

    ReplyDelete
  18. This Content is simply amazing, and helpful for students and business people. Gathered lots of information and waiting to see more updates.
    RPA Training in Chennai
    RPA Online Course
    RPA Training in Coimbatore

    ReplyDelete
  19. It was easy for me to follow your article . I hope to see more articles similar to yours . It was easy to apply online for an e visa of Turkey and it saved me time and money at the same time.

    ReplyDelete