Question

I'm dealing with some cyclic references while implementing my project's web service layer. I'm using jaxb (latest version, 2.2.7) and even I had a look to some tips as here and here I can't get it working. That's a basic SSCCE about my problem:

/*
* The service interface
*/
@WebService
public interface IMyWS {

    @WebMethod
    public List<Class1> cyclicTest();

}

/*
* Interface implementation
*/
@WebService(endpointInterface = "com.mycompany.ws.interfaces.IMyWS")
public class MyWS implements IMyWS {

    @XmlRootElement
    public static class Class1 {

        @XmlTransient
        private Class2 class2;

        public Class1() {

        }

        public Class1(Class2 refClass) {
            class2 = refClass;
        }

        public Class2 getClass2() {
            return class2;
        }

        public void setClass2(Class2 class2) {
            this.class2 = class2;
        }

        @Override
        public String toString() {
            return this.getClass().getSimpleName();
        }

    }

    @XmlRootElement
    public static class Class2 {

        private Class1 class1;

        public Class2() {

        }

        public Class1 getClass1() {
            return class1;
        }

        public void setClass1(Class1 class1) {
            this.class1 = class1;
        }

        @Override
        public String toString() {
            return this.getClass().getSimpleName();
        }
    }

    @Override
    public List<Class1> cyclicTest() {
        //I create an instance of each class, having them a cyclic reference to the other instance
        Class2 class2 = new Class2();
        Class1 class1 = new Class1(class2);
        class2.setClass1(class1);
        return Arrays.asList(class1);
    }

}

And the exception I'm actually dealing with when calling cyclicTest():

Caused by: javax.xml.bind.MarshalException
 - with linked exception:
[com.sun.istack.SAXException2: Se ha detectado un ciclo en el gráfico de objeto. Esto provocará un XML con profundidad infinita: Class1 -> Class2 -> Class1]
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:326)
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:178)
    at org.apache.cxf.jaxb.JAXBEncoderDecoder.writeObject(JAXBEncoderDecoder.java:537)
    at org.apache.cxf.jaxb.JAXBEncoderDecoder.marshall(JAXBEncoderDecoder.java:233)
    ... 50 more
Caused by: com.sun.istack.SAXException2: Se ha detectado un ciclo en el gráfico de objeto. Esto provocará un XML con profundidad infinita: Class1 -> Class2 -> Class1
    at com.sun.xml.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:249)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.pushObject(XMLSerializer.java:537)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:631)
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:158)
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:361)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:158)
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:361)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
    at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:69)
    at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:172)
    at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:159)
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:361)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:156)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:131)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:333)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:340)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:76)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:494)
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:323)
    ... 53 more

I think I have the proper annotations set. What am I actually missing?

Was it helpful?

Solution 2

As you add @XmlTransient on a private property, you should change your XmlAccessType (default to XmlAccessType.PUBLIC_MEMBER) to XmlAccessType.PROPERTY

PUBLIC_MEMBER is the default access type in JAXB. It means that a JAXB implementation will generate bindings for: public fields, annotated fields, properties

code copied from W A :

@WebService(endpointInterface = "com.mycompany.ws.interfaces.IMyWS")
public class MyWS implements IMyWS {

@XmlRootElement
@XmlAccessorType(XmlAccessType.PROPERTY)
public static class Class1 {

    private Class2 class2;

    public Class1() {

    }

    public Class1(Class2 refClass) {
        class2 = refClass;
    }

    @XmlTransient
    public Class2 getClass2() {
        return class2;
    }

    public void setClass2(Class2 class2) {
        this.class2 = class2;
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }

}

@XmlRootElement
public static class Class2 {

    private Class1 class1;

    public Class2() {

    }

    public Class1 getClass1() {
        return class1;
    }

    public void setClass1(Class1 class1) {
        this.class1 = class1;
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }
}

public List<Class1> cyclicTest() {
    //I create an instance of each class, having them a cyclic reference to the other instance
    Class2 class2 = new Class2();
    Class1 class1 = new Class1(class2);
    class2.setClass1(class1);
    return Arrays.asList(class1);
}


public static void main(String[] args) throws JAXBException {

    JAXBContext ctx = JAXBContext.newInstance(Class1.class);
    Marshaller m = ctx.createMarshaller();
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    List<Class1> class1s = new MyWs().cyclicTest();

    for (Class1 c1 : class1s){
        m.marshal(c1, System.out);
    }

}

OTHER TIPS

Using @XmlID and @XmlIDREF should solve your problem.

Here's what I did:

I added a String field to Class 1 to be used as an id, and annotated it with @XmlID. Then, I annotated the setClass1() method with @XmlIDREF. My test is in the main method below.

/*
 * Interface implementation
 */
@WebService(endpointInterface = "com.mycompany.ws.interfaces.IMyWS")
public class MyWS implements IMyWS {

    @XmlRootElement
    public static class Class1 {

        @XmlID
        private String id;

        private Class2 class2;

        public Class1() {
            this.id = UUID.randomUUID().toString();
        }

        public Class1(Class2 refClass) {
            this();
            class2 = refClass;
        }

        public Class2 getClass2() {
            return class2;
        }

        public void setClass2(Class2 class2) {
            this.class2 = class2;
        }

        @Override
        public String toString() {
            return this.getClass().getSimpleName();
        }

    }

    @XmlRootElement
    public static class Class2 {

        private Class1 class1;

        public Class2() {

        }

        public Class1 getClass1() {
            return class1;
        }

        @XmlIDREF
        public void setClass1(Class1 class1) {
            this.class1 = class1;
        }

        @Override
        public String toString() {
            return this.getClass().getSimpleName();
        }
    }

    @Override
    public List<Class1> cyclicTest() {
        //I create an instance of each class, having them a cyclic reference to the other instance
        Class2 class2 = new Class2();
        Class1 class1 = new Class1(class2);
        class2.setClass1(class1);
        return Arrays.asList(class1);
    }

    public static void main(String[] args) throws JAXBException {

        JAXBContext ctx = JAXBContext.newInstance(Class1.class);
        Marshaller m = ctx.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        List<Class1> class1s = new MyWS().cyclicTest();

        for (Class1 c1 : class1s){
            m.marshal(c1, System.out);
        }

    }

}

Output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<class1>
    <id>e9e53e73-9a96-4c7c-b919-3ed3d7aa4c5e</id>
    <class2>
        <class1>e9e53e73-9a96-4c7c-b919-3ed3d7aa4c5e</class1>
    </class2>
</class1>

Adding to the answer by W Almir, to make generation of unique IDs for objects simpler (no need to add code to constructors), annotate a method that returns the id. For example:

@XmlID
public String getId()
{
    return Integer.toString(System.identityHashCode(this));
}

This seems to work not only for converting cyclical graphs of Java objects to XML but also for converting XML to cyclical graphs of Java objects.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top