'JAXB: Marshalling changes object type to String for XmlIDREF

I am facing a problem when serializing an object. The object is a representation of a stack. The Stack can either consist of individual Layers or a sequence of Layers stored in a LayerSequence. So, Layer and LayerSequence implement Stackable and the Stack holds a List of Stackable. Since Layer and LayerSequence are stored independently of Stack, I use XmlIDREF to reference them by a name.

To help JAXB resolving the interface implementations, I define

    @XmlElementWrapper(name="layers")
    @XmlElements({
        @XmlElement(name="layer", type=Layer.class)
       ,@XmlElement(name="layerSequence", type=LayerSequence.class)
    })
    @XmlIDREF
    private List<S> layers;

When serializing with a generic Stack<? extends Stackable> the type is not transferred as I would have expected. The type is changed to a String containing the XmlID. The result is:

<stack name="stack">
    <layers>
        <layer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">layer</layer>
    </layers>
</stack>

For a specific example, where I know, that only Layers and no LayerSequences are used and I define

    @XmlElementWrapper(name="layers")
    @XmlElements({
        @XmlElement(name="layer", type=Layer.class)
    })
    @XmlIDREF
    private List<S> layers;

the type is properly defined as expected:

<stack name="stack">
    <layers>
        <layer>layer</layer>
    </layers>
</stack>

The same works the other way around for LayerSequence.

What am I missing here? Is this a problem because LayerSequence itself uses Layers? How can I circumvent this?


MWE

Code

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlIDREF;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;

public class Main
{   
    
    public static void main(String[] args) throws PropertyException, JAXBException{
        
        LayerValueImpl layerValue1 = new LayerValueImpl("layerValue");
        
        Layer<LayerValueImpl> layer = new Layer<>("layer",layerValue1);
        
        List<Layer<? extends AbstractLayerValue>> layers = new ArrayList<>();
        layers.add(layer);
        
        Stack<? extends Stackable> stack = new Stack<>("stack",layers);
        
        //
        JAXBContext jc = JAXBContext.newInstance(Stack.class);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(stack, System.out);
    }
    
    public static interface Identifiable<
                T extends Identifiable
            >
    {
        
        public void setName(String name);
        public String getName();
    }
    
    public static interface Stackable<
                T extends Stackable
            >
            extends
                Identifiable<T>
    {
        
        @XmlAttribute
        @XmlID
        @Override
        public String getName(); 
        
    }
    
    @XmlTransient
    @XmlAccessorType(XmlAccessType.FIELD)
    public static abstract class AbstractLayerValue<
                T extends AbstractLayerValue
            >
            implements
                Identifiable<T>
    {
        
        @XmlAttribute
        @XmlID
        private String name;

        public AbstractLayerValue() {}
        
        public AbstractLayerValue(
                String name
        ) {
            this.name = name;
        }
        
        @Override
        public void setName(String v){this.name = v;}
        @Override
        public String getName(){return name;}
        
    }
    
    @XmlRootElement
    public static class LayerValueImpl
            extends
                AbstractLayerValue<
                    LayerValueImpl
                >
    {

        public LayerValueImpl() {}

        public LayerValueImpl(String name) {
            super(name);
        }
        
    }
    
    @XmlTransient
    @XmlAccessorType(XmlAccessType.FIELD)
    public static abstract class AbstractLayerSuperClass<
                T extends AbstractLayerSuperClass
            >
            implements
                Identifiable<T>
    {
        
        @XmlAttribute
        @XmlID
        private String name;

        public AbstractLayerSuperClass() {}

        public AbstractLayerSuperClass(
                String name
        ) {
            this.name = name;
        }
        
        @Override
        public void setName(String v){this.name = v;}
        @Override
        public String getName(){return name;}
    }
    
    @XmlRootElement
    @XmlSeeAlso({
        LayerValueImpl.class
    })
    public static class Layer<
                V extends AbstractLayerValue
            >
            extends
                AbstractLayerSuperClass<
                    Layer<V>
                >
            implements
                Stackable<Layer<V>>
    {
        
        private V value;

        public Layer() {}

        public Layer(
                String name
               ,V value
        ) {
            super(
                    name
            );
            this.value = value;
        }
        
        public void setValue(V v){this.value = v;}
        public V getValue(){return value;}
    }
    
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class LayerSequence
            implements
                Stackable<LayerSequence>
    {
        
        @XmlAttribute
        @XmlID
        private String name;
        private List<Layer<? extends AbstractLayerValue>> layers;

        public LayerSequence() {}

        public LayerSequence(
                String name
               ,List<Layer<? extends AbstractLayerValue>> layers
        ) {
            this.name = name;
            this.layers = layers;
        }

        @Override
        public void setName(String v){this.name = v;}
        @Override
        public String getName(){return name;}
        
        public void setLayers(List<Layer<? extends AbstractLayerValue>> v){this.layers = v;}
        public List<Layer<? extends AbstractLayerValue>> getLayers(){return layers;}
        
    }
    
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class Stack<
                S extends Stackable
            >
            implements
                Identifiable<Stack<S>>
    {
        
        @XmlAttribute
        @XmlID
        private String name;
        
        @XmlElementWrapper(name="layers")
        @XmlElements({
            @XmlElement(name="layer", type=Layer.class)
           ,@XmlElement(name="layerSequence", type=LayerSequence.class)
        })
        //@XmlElementRefs({
        //    @XmlElementRef(type=Layer.class, name="layer"),
        //    @XmlElementRef(type=LayerSequence.class, name="layerSequence")
        //})
        @XmlIDREF
        private List<S> layers;

        public Stack() {}

        public Stack(
                String name
               ,List<S> layers
        ) {
            this.name = name;
            this.layers = layers;
        }
        
        @Override
        public void setName(String v){this.name = v;}
        @Override
        public String getName(){return name;}
        
        public void setLayers(List<S> v){this.layers = v;}
        public List<S> getLayers(){return layers;}
        
    }

}

POM

<?xml version="1.0" encoding="UTF-8"?>
<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>de.mr</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>
    
    <!-- PROPERTIES -->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- System    -->
        <java.version>16</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <maven.plugin.compiler.version>3.8.1</maven.plugin.compiler.version>
        <maven.plugin.enforcer.version>3.0.0-M2</maven.plugin.enforcer.version>
        <maven.plugin.jar.version>3.2.0</maven.plugin.jar.version>
        <maven.plugin.release.version>2.5.3</maven.plugin.release.version>
        <!-- Log4j2 -->
        <log4j.api.version>2.13.1</log4j.api.version>
        <log4j.core.version>2.13.1</log4j.core.version>
        <!-- Lombok -->
        <lombok.version>1.18.22</lombok.version>
        <maven.plugin.lombok.version>1.18.20.0</maven.plugin.lombok.version>
        <!-- Stuff for JAXB -->
        <javax.xml.bind.jaxb-api.version>2.4.0-b180830.0359</javax.xml.bind.jaxb-api.version>
        <javax.activation.activation.version>1.1</javax.activation.activation.version>
        <org.glassfish.jaxb.jaxb-runtime.version>2.3.0-b170127.1453</org.glassfish.jaxb.jaxb-runtime.version>
        <maven.plugin.jaxb2.version>2.5.0</maven.plugin.jaxb2.version>
        <maven.plugin.jaxb2.locale>en</maven.plugin.jaxb2.locale>
        <maven.plugin.jaxb2.clearOutputDir>true</maven.plugin.jaxb2.clearOutputDir>
        <maven.plugin.jaxb2.createJavaDocAnnotations>false</maven.plugin.jaxb2.createJavaDocAnnotations>
    </properties>
    
    <!-- DEPENDENCIES -->
    <dependencies>
        <!--           -->
        <!-- 3rd party -->
        <!--           -->
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- Log4j2 -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j.api.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j.core.version}</version>
        </dependency>
        <!-- Stuff for JAXB -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>${javax.xml.bind.jaxb-api.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>${javax.activation.activation.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>${org.glassfish.jaxb.jaxb-runtime.version}</version>
        </dependency>
    </dependencies>
    
    <!-- BUILD -->
    <build>
        <!-- PLUGINS -->
        <plugins>
            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.plugin.compiler.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-enforcer-plugin</artifactId>
                <version>${maven.plugin.enforcer.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>enforce</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <rules>
                        <requireJavaVersion>
                            <version>${java.version}</version>
                        </requireJavaVersion>
                    </rules>
                </configuration>
            </plugin>
            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-release-plugin</artifactId>
                <version>${maven.plugin.release.version}</version>
                <configuration>
                    <localCheckout>true</localCheckout>
                    <pushChanges>false</pushChanges>
                </configuration>
            </plugin>
            
            <!-- Delombok -->
            <plugin>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok-maven-plugin</artifactId>
                <version>${maven.plugin.lombok.version}</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>delombok</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <addOutputDirectory>false</addOutputDirectory>
                    <sourceDirectory>src/main/java</sourceDirectory>
                    <withoutUnicodeEscape>true</withoutUnicodeEscape>
                </configuration>
            </plugin>
            
            <!-- Generate a XSD schema -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jaxb2-maven-plugin</artifactId>
                <version>${maven.plugin.jaxb2.version}</version>
                <executions>
                    <execution>
                        <id>schemagen</id>
                        <goals>
                            <goal>schemagen</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <sources>
                        <source>src/main/java/de/mr/test/jaxbschemagenlombok</source>
                    </sources>
                    <outputDirectory>${project.build.directory}/generated-sources/schemas</outputDirectory>
                    <clearOutputDir>${maven.plugin.jaxb2.clearOutputDir}</clearOutputDir>
                    <createJavaDocAnnotations>${maven.plugin.jaxb2.createJavaDocAnnotations}</createJavaDocAnnotations>
                    <locale>${maven.plugin.jaxb2.locale}</locale>
                </configuration>
            </plugin>
        </plugins>
        
        <!-- THIS IS THE FINAL NAME OF THE JAR -->
        <finalName>${project.artifactId}-${project.version}</finalName>
        
    </build>
</project>


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source