javamavenxsdjaxb

jaxb-maven-plugin xsd to java: how to solve xsd with root element and child element having the same name


I have a similar structure as this xsd:

<xs:element name="address">
  <xs:complexType>
    <xs:choice>
      <xs:element name="address">
        <xs:complexType>
          <xs:sequence>
            <!-- ... -->
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="mailbox">
        <xs:complexType>
          <xs:sequence>
            <!-- ... -->
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:choice>
  </xs:complexType>
</xs:element>

In my opinion this is a bad schema. You should not name your elements in that way. I cannot change this schema since its comming from a third party. I have to convert this into a java model. Obviously this wont work out of the box. JAXB XSD to Java would try to generate something like this:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "address",
    "mailbox"
})
public static class Address implements Serializable, Cloneable, CopyTo, Equals, HashCode, MergeFrom, ToString
{

    Address.Address address;
    Address.Mailbox mailbox;

    public static class Address implements Serializable, Cloneable, CopyTo, Equals, HashCode, MergeFrom, ToString
    {
        // ...
    }
    
    public static class Mailbox implements Serializable, Cloneable, CopyTo, Equals, HashCode, MergeFrom, ToString
    {
        // ...
    }
}

Ofcourse this results in the java error Duplicate class: 'Address'. Since I cannot change the schema I try to change the generated java class. I remembered there was a plugin for auto name resolution. So I tried:

<plugin>
  <groupId>org.jvnet.jaxb</groupId>
  <artifactId>jaxb-maven-plugin</artifactId>
  <version>4.0.9</version>
  <executions>
    <execution>
      <goals>
        <goal>generate</goal>
      </goals>
      <configuration>
        <debug>true</debug>
        <args>
          <!-- Enable Equals Generation -->
          <arg>-Xequals</arg>
          <!-- Enable Hashcode Generation -->
          <arg>-XhashCode</arg>
          <!-- Enable ToString Generation -->
          <arg>-XtoString</arg>
          <!-- Enable Builder Generation -->
          <arg>-Xfluent-api</arg>
          <!-- Enable Inheritance Generation -->
          <arg>-Xinheritance</arg>
          <!-- Enable Copy Generation -->
          <arg>-Xcopyable</arg>
          <!-- Enable Merge Generation -->
          <arg>-Xmergeable</arg>
          <!-- auto resolve conflicting names -->
          <arg>-XautoNameResolution</arg>
        </args>
        <strict>false</strict>
        <extension>true</extension>
        <schemaDirectory>src/main/resources/xsd</schemaDirectory>
        <bindingDirectory>src/main/resources/xjb</bindingDirectory>
        <catalog>src/main/resources/xsd/external/catalog.cat</catalog>
        <generateDirectory>${project.build.directory}/generated-sources</generateDirectory>
        <plugins>
          <plugin>
            <groupId>org.jvnet.jaxb</groupId>
            <artifactId>jaxb-plugins</artifactId>
            <version>4.0.9</version>
          </plugin>
          <plugin>
            <groupId>org.jvnet.jaxb</groupId>
            <artifactId>jaxb-plugins-tools</artifactId>
            <version>4.0.9</version>
          </plugin>
        </plugins>
      </configuration>
    </execution>
  </executions>
</plugin>

But that did not change the java class generation at all. So I tried the bindings approach. I did the following:

<jxb:bindings version="3.0"
              xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
              xmlns:xs="http://www.w3.org/2001/XMLSchema">
  
  <jxb:bindings schemaLocation="schema.xsd" node="/xs:schema/xs:element[@name='address']">
    <jxb:class name="Address"/>
  </jxb:bindings>

  <jxb:bindings schemaLocation="schema.xsd" node="/xs:schema/xs:element[@name='address']/xs:complexType/xs:choice/xs:element[@name='address']">
    <jxb:class name="AddressDetails"/>
  </jxb:bindings>

</jxb:bindings>

But this results in: Execution default of goal org.jvnet.jaxb:jaxb-maven-plugin:4.0.0:generate failed: Illegal class inheritance loop. Outer class Address may not subclass from inner class: Address

Im not sure what is really happening there.

Is there any way to solve that problem?


Solution

  • Mulgard, you are very close. To demonstrate a solution, I completed your sample schema, as follows:

    SameRootChildName.xsd

    <?xml version="1.0" encoding="UTF-8" ?>
    <xs:schema elementFormDefault="qualified"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        targetNamespace="urn:example.org:srcn"
        xmlns:tns="urn:example.org:srcn"
    >
    
        <xs:element name="address">
            <xs:complexType>
                <xs:choice>
                    <xs:element name="address">
                        <xs:complexType>
                            <xs:sequence>
                                <xs:element name="ellipsis" type="xs:string"/>
                            </xs:sequence>
                        </xs:complexType>
                    </xs:element>
                    <xs:element name="mailbox">
                        <xs:complexType>
                            <xs:sequence>
                                <xs:element name="ellipsis" type="xs:string"/>
                            </xs:sequence>
                        </xs:complexType>
                    </xs:element>
                </xs:choice>
            </xs:complexType>
        </xs:element>
    
    </xs:schema>
    

    Using this example, I am able to reproduce your error using the xjc command line tool:

    xjc -d out -no-header SameRootChildName.xsd
    
    parsing a schema...
    compiling a schema...
    [ERROR] Complex type and its child element share the same name "Address". Use a class customization to resolve this conflict.
      line 12 of file:SameRootChildName.xsd
    
    Failed to produce code.
    

    Perhaps, this error message is more direct, "Complex type and its child element share ..." The complex type in the message is a reference to the anonymous type of the root level element named address. Here is a binding file that targets that type and binds a class customization to it. The customization changes the generated root class name to AddressRoot and leaves the child class name as Address

    SameRootChildName.xjb

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <jaxb:bindings jaxb:version="3.0"
        xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
    >
    
        <jaxb:globalBindings localScoping="toplevel" >
            <jaxb:serializable uid="20250501" />
        </jaxb:globalBindings>
    
        <jaxb:bindings schemaLocation="SameRootChildName.xsd" node="/xs:schema">
            <jaxb:bindings node="xs:element[@name='address']" >
                <jaxb:class name="AddressRoot"/>
            </jaxb:bindings>
        </jaxb:bindings>
    
    </jaxb:bindings>
    

    Re-executing the `xjc' tool with the new binding file generated the classes successfully.

    xjc -d out -no-header -b SameRootChildName.xjb SameRootChildName.xsd 
    parsing a schema...
    compiling a schema...
    org/example/srcn/Address.java
    org/example/srcn/AddressRoot.java
    org/example/srcn/Mailbox.java
    org/example/srcn/ObjectFactory.java
    org/example/srcn/package-info.java
    

    This edit to the bindings file above works too:

    SameRootChildName.xjb (edit)

    ...
    <jaxb:bindings node="xs:element[@name='address']" >
        <jaxb:bindings node="xs:complexType" >
            <jaxb:class name="AddressRoot"/>
        </jaxb:bindings>
    </jaxb:bindings>
    ...
    

    Alternatively, this variant of the bindings file works by changing the name of the child class instead of the root class.

    SameRootChildName2.xjb

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <jaxb:bindings jaxb:version="3.0"
        xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
    >
    
        <jaxb:globalBindings localScoping="toplevel" >
            <jaxb:serializable uid="20250501" />
        </jaxb:globalBindings>
    
        <jaxb:bindings schemaLocation="SameRootChildName.xsd" node="/xs:schema">
            <jaxb:bindings node="xs:element[@name='address']" >
                <jaxb:bindings node="xs:complexType" >
                    <jaxb:bindings node="xs:choice" >
                        <jaxb:bindings node="xs:element[@name='address']" >
                            <jaxb:bindings node="xs:complexType" >
                                <jaxb:class name="AddressDetails"/>
                            </jaxb:bindings>
                        </jaxb:bindings>
                    </jaxb:bindings>
                </jaxb:bindings>
            </jaxb:bindings>
        </jaxb:bindings>
    
    </jaxb:bindings>
    

    The result is shown here:

    xjc -d out -no-header -b SameRootChildName2.xjb SameRootChildName.xsd
    
    parsing a schema...
    compiling a schema...
    org/example/srcn/Address.java
    org/example/srcn/AddressDetails.java
    org/example/srcn/Mailbox.java
    org/example/srcn/ObjectFactory.java
    org/example/srcn/package-info.java