No @XmlRootElement generated by JAXB

Viewed 290341

I'm trying to generate Java classes from the FpML (Finanial Products Markup Language) version 4.5. A ton of code is generated, but I cannot use it. Trying to serialize a simple document I get this:

javax.xml.bind.MarshalException
  - with linked exception: [com.sun.istack.SAXException2: unable
  to marshal type
  "org.fpml._2008.fpml_4_5.PositionReport"
  as an element because it is missing an
  @XmlRootElement annotation]

In fact no classses have the @XmlRootElement annotation, so what can I be doing wrong?. I'm pointing xjc (JAXB 2.1) to fpml-main-4-5.xsd, which then includes all types.

17 Answers

To tie together what others have already stated or hinted at, the rules by which JAXB XJC decides whether or not to put the @XmlRootElement annotation on a generated class are non trivial (see this article).

@XmlRootElement exists because the JAXB runtime requires certain information in order to marshal/unmarshal a given object, specifically the XML element name and namespace. You can't just pass any old object to the Marshaller. @XmlRootElement provides this information.

The annotation is just a convenience, however - JAXB does not require it. The alternative to is to use JAXBElement wrapper objects, which provide the same information as @XmlRootElement, but in the form of an object, rather than an annotation.

However, JAXBElement objects are awkward to construct, since you need to know the XML element name and namespace, which business logic usually doesn't.

Thankfully, when XJC generates a class model, it also generates a class called ObjectFactory. This is partly there for backwards compatibility with JAXB v1, but it's also there as a place for XJC to put generated factory methods which create JAXBElement wrappers around your own objects. It handles the XML name and namespace for you, so you don't need to worry about it. You just need to look through the ObjectFactory methods (and for large schema, there can be hundreds of them) to find the one you need.

Joe's answer (Joe Jun 26 '09 at 17:26) does it for me. The simple answer is that absence of an @XmlRootElement annotation is no problem if you marshal a JAXBElement. The thing that confused me is the generated ObjectFactory has 2 createMyRootElement methods - the first takes no parameters and gives the unwrapped object, the second takes the unwrapped object and returns it wrapped in a JAXBElement, and marshalling that JAXBElement works fine. Here's the basic code I used (I'm new to this, so apologies if the code's not formatted correctly in this reply), largely cribbed from link text:

ObjectFactory objFactory = new ObjectFactory();
MyRootElement root = objFactory.createMyRootElement();
...
// Set root properties
...
if (!writeDocument(objFactory.createMyRootElement(root), output)) {
    System.err.println("Failed to marshal XML document");
}
...

private boolean writeDocument(JAXBElement document, OutputStream output) {

  Class<?> clazz = document.getValue().getClass();
  try {
    JAXBContext context =
        JAXBContext.newInstance(clazz.getPackage().getName());
    Marshaller m = context.createMarshaller();
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    m.marshal(document, output);
    return true;

  } catch (JAXBException e) {
    e.printStackTrace(System.err);
    return false;
  }
}

The topic is quite old but still relevant in enterprise business contexts. I tried to avoid to touch the xsds in order to easily update them in the future. Here are my solutions..

1. Mostly xjc:simple is sufficient

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jxb:bindings version="2.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
    jxb:extensionBindingPrefixes="xjc">

    <jxb:globalBindings>
        <xjc:simple/> <!-- adds @XmlRootElement annotations -->
    </jxb:globalBindings>

</jxb:bindings>

It will mostly create XmlRootElements for importing xsd definitions.

2. Divide your jaxb2-maven-plugin executions

I have encountered that it makes a huge difference if you try to generate classes from multiple xsd definitions instead of a execution definition per xsd.

So if you have a definition with multiple <source>'s, than just try to split them:

          <execution>
            <id>xjc-schema-1</id>
            <goals>
              <goal>xjc</goal>
            </goals>
            <configuration>
              <xjbSources>
                <xjbSource>src/main/resources/xsd/binding.xjb</xjbSource>
              </xjbSources>
              <sources>
                <source>src/main/resources/xsd/definition1/</source>
              </sources>
              <clearOutputDir>false</clearOutputDir>
            </configuration>
          </execution>

          <execution>
            <id>xjc-schema-2</id>
            <goals>
              <goal>xjc</goal>
            </goals>
            <configuration>
              <xjbSources>
                <xjbSource>src/main/resources/xsd/binding.xjb</xjbSource>
              </xjbSources>
              <sources>
                <source>src/main/resources/xsd/definition2/</source>
              </sources>
              <clearOutputDir>false</clearOutputDir>
            </configuration>
          </execution>

The generator will not catch the fact that one class might be sufficient and therefore create custom classes per execution. And thats exactly what I need ;).

So I was using mavens maven-jaxb2-plugin to generate the classes from a large & complicated WSDL file and run into this issue. The problem being that elements in the WSDL referenced complexType definitions as the type and therefore the element classes where not generated and when trying to use the complexType classes it produced the missing @XmlRootElement error.

Modifying the WSDL is not really a viable solution in my oppinion, the only thing practical seemed to engineer a way to add the missing annotation during generation. It also caused problems with the serialization when marshalling because the request was sending the wrong element name and there was no class with a matching element name for the response either.

What I ended up doing was using a second maven plugin jaxb2-basics-annotate which allows you to add the missing annotations to the required classes through the use of a jaxb binding file. This saves you having to sort this problem out without adding unnecessary code and also means you can re-generate easily if you need to use an updated WSDL file in the future.

pom.xml (Note that there is a plugin section in the configuration for the execution - The WSDL file location is /src/main/resources/wsdl/EstimatingService.wsdl)

<project>
    ...
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <jaxb2.version>0.14.0</jaxb2.version>
        <jaxb2.annotate.version>1.1.0</jaxb2.annotate.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.jvnet.jaxb2.maven2</groupId>
            <artifactId>maven-jaxb2-plugin</artifactId>
            <version>${jaxb2.version}</version>
        </dependency>
        <dependency>
            <groupId>org.jvnet.jaxb2_commons</groupId>
            <artifactId>jaxb2-basics-annotate</artifactId>
            <version>${jaxb2.annotate.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <version>${jaxb2.version}</version>
                <executions>
                    <execution>
                        <id>estimating</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <schemaLanguage>WSDL</schemaLanguage>
                            <generateDirectory>target/generated-sources/acme/src/gen/estimating-service</generateDirectory>
                            <generatePackage>com.acme.shipping.estimating.service</generatePackage>
                            <schemaDirectory>${project.basedir}/src/main/resources/wsdl</schemaDirectory>
                            <schemaIncludes>
                                <include>EstimatingService.wsdl</include>
                            </schemaIncludes>
                            <bindingDirectory>${project.basedir}/src/main/resources/bindings</bindingDirectory>
                            <bindingIncludes>estimateServiceBinding.xjb</bindingIncludes>
                            <extension>true</extension>
                            <args>
                                <arg>-Xannotate</arg>
                                <arg>-XremoveAnnotation</arg>
                            </args>
                            <plugins>
                                <plugin>
                                    <groupId>org.jvnet.jaxb2_commons</groupId>
                                    <artifactId>jaxb2-basics-annotate</artifactId>
                                </plugin>
                            </plugins>
                        </configuration>
                    </execution>
                    ...
                    // More executions here if you have multiple WSDL files (Dont forget to give it a different package name and id)
                </executions>
            </plugin>
        </plugins>
    </build>
    ...
</project>  

estimateServiceBinding.xjb (jaxb binding file used in this example - /src/main/resources/bindings/estimateServiceBinding.xjb)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jaxb:bindings version="2.0" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:annox="http://annox.dev.java.net" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
    <jaxb:globalBindings generateElementProperty="false">
        <xjc:simple />
    </jaxb:globalBindings>
    <!-- Target the schema section in the WSDL file using the given target namespace which contains the complexType definitions we want to annotate -->
    <jaxb:bindings schemaLocation="../wsdl/EstimatingService.wsdl" node="//xs:schema[@targetNamespace='http://acme.com/schema/datatypes/v2']">
        <jaxb:bindings node="xs:complexType[@name='GetQuickEstimateRequestContainer']">
            <!-- Add the @XmlRootElement annotation to the generated class and then tell it use the correct element name required when marshalling. e.g GetQuickEstimateRequestContainer element is renamed to the element name that referenced it in the WSDL (GetQuickEstimateRequest) -->
            <annox:annotateClass>@javax.xml.bind.annotation.XmlRootElement(name="GetQuickEstimateRequest")</annox:annotateClass>
        </jaxb:bindings>
        <jaxb:bindings node="xs:complexType[@name='GetQuickEstimateResponseContainer']">
            <annox:annotateClass>@javax.xml.bind.annotation.XmlRootElement(name="GetQuickEstimateResponse")</annox:annotateClass>
        </jaxb:bindings>
    </jaxb:bindings>
</jaxb:bindings>

Generated class (GetQuickEstimateRequestContainer.java) with @XmlRootElement annotation and correct element name

@XmlRootElement(name = "GetQuickEstimateRequest")
public class GetQuickEstimateRequestContainer {
    ...
    // class member fields & setters and getters
    ...
}

I just was struggling for a while with the same problem and just want to post my final result which works fine for me. So the base problems have been:

  • I have to generate xml strings from JAXB class instances with have no XmlRootElement annotations
  • The classes need additional classes to be bound for the marshalling process

The following class works fine for this problem:

public class Object2XmlConverter {

    public static <T> String convertToString(final T jaxbInstance, final Class<?>... additionalClasses)
            throws JAXBException {
        final Class<T> clazz = (Class<T>) jaxbInstance.getClass();

        final JAXBContext jaxbContext;
        if (additionalClasses.length > 0) {
            // this path is only necessary if you need additional classes to be bound
            jaxbContext = JAXBContext.newInstance(addClassesToBeBound(clazz, additionalClasses));
        } else {
            jaxbContext = JAXBContext.newInstance(clazz);
        }

        final QName qname = new QName("", jaxbInstance.getClass().getSimpleName());
        final JAXBElement<T> jaxbElement = new JAXBElement<T>(qname, clazz, null, jaxbInstance);

        final Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

        final StringWriter stringWriter = new StringWriter();
        jaxbMarshaller.marshal(jaxbElement, stringWriter);
        return stringWriter.toString();
    }

    private static <T> Class<?>[] addClassesToBeBound(final Class<T> clazz, final Class<?>[] additionalClasses) {
        final Class<?>[] classArray = new Class<?>[additionalClasses.length + 1];
        for (int i = 0; i < additionalClasses.length; i++) {
            classArray[i] = additionalClasses[i];
        }
        classArray[classArray.length - 1] = clazz;
        return classArray;
    }

    public static void main(final String[] args) throws Exception {
        final Ns1TargetHeaderTyp dataTyp = ...;
        System.out.println(convertToString(dataTyp));
    }
}
Related