19

I have an object graph that contains a cycle. How do I get JAXB to handle this? I tried using the @XmlTransient annotation in the child class but the JAXB marshaller still detects the cycle.

@Entity
@XmlRootElement
public class Contact {

    @Id
    private Long contactId;

    @OneToMany(mappedBy = "contact")
    private List<ContactAddress> addresses;

...

}

@Entity
@XmlRootElement
public class ContactAddress {

    @Id
    private Long contactAddressId;

    @ManyToOne
    @JoinColumn(name = "contact_id")
    private Contact contact;

    private String address;

...

}
Jordan Allan
  • 4,408
  • 7
  • 32
  • 35

5 Answers5

17

This page in the "Unofficial JAXB Guide" offers three strategies for dealing with cycles. They are (in summary):

  • Mark one of the reference attributes that form the cycle as @XmlTransient.
  • Use @XmlID and @XmlIDREF so that the references are represented using XML ids arather than by containment.
  • Use the CycleRecoverable interface to deal with cycles programmatically.
Vineet Reynolds
  • 76,006
  • 17
  • 150
  • 174
Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • XmlID and XmlIDREF are easy to use. Add the XmlID annotation to a method public String getId() { return Integer.toString(System.identityHashCode(this)); } and you get a simple way to assign unique IDs to your XML elements. – astraujums Nov 07 '17 at 00:50
13

The good thing about using JAXB is that it is a standard runtime with multiple implementations (just like JPA).

If you use EclipseLink JAXB (MOXy) then you have many extensions available to you for handling JPA entities including bi-directional relationships. This is done using the MOXy @XmlInverseReference annotation. It acts similar to @XmlTransient on the marshal and populates the target-to-source relationship on the unmarshal.

http://wiki.eclipse.org/EclipseLink/Examples/MOXy/JPA/Relationships

@Entity 
@XmlRootElement 
public class Contact { 

    @Id 
    private Long contactId; 

    @OneToMany(mappedBy = "contact") 
    private List<ContactAddress> addresses; 

... 

} 

@Entity 
@XmlRootElement 
public class ContactAddress { 

    @Id 
    private Long contactAddressId; 

    @ManyToOne 
    @JoinColumn(name = "contact_id") 
    @XmlInverseReference(mappedBy="addresses")
    private Contact contact; 

    private String address; 

... 

} 

Other extensions are available including support for composite keys & embedded key classes.

To specify the EcliseLink MOXy JAXB implementation you need to include a jaxb.properties file in with your model classes (i.e. Contract) with the following entry:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • Perhaps this is a JAX-RS issue. I still get the exception saying a cycle was detected - javax.ws.rs.WebApplicationException: javax.xml.bind.MarshalException - with linked exception: [com.sun.istack.SAXException2: A cycle is detected in the object graph. – Jordan Allan Jul 06 '10 at 21:49
  • You will need to add a jaxb.properties file specifying the EclipseLink MOXy runtime. I have added the instructions to my answer above. – bdoughan Jul 07 '10 at 14:24
  • How do you include jaxb.properties in the entity classes? – Nick Oct 19 '10 at 18:02
  • 2
    jaxb.properties is a text file with one property, that needs to be added into the same package as the entity classes: http://bdoughan.blogspot.com/2010/08/creating-restful-web-service-part-35.html – bdoughan Oct 19 '10 at 18:37
  • For the life of me I can not get this working in my project. I've put the jaxb.properties file with my entities, have the libraries in maven and have almost the exact class layout Blaise listed... however I still get Hibernate complaining my @XmlInverseReference property is null :(. – danieljimenez May 24 '12 at 16:16
  • @BlaiseDoughan I have created the library for all entities and repositories, and created a jar file using spring boot. But jax.properties is not added to the library. Do you have any idea on why it happened ? – Krish Aug 18 '17 at 20:51
6

XMLTransient almost always works for cycles. It might be a possibility that you have XMLTransient on the field level but you have not specified XMLAccessorType to be XmlAccessType.Field. If you don't specify anything the default is XmlAccessType.Property - or your getters. I have experienced Jaxb picking xml elements from getters from a class that I missed the accessor type annotations on and still work perfectly fine.

Gunjan Kalra
  • 437
  • 5
  • 10
1

We can use XStream library as well, I tried it one project where JAXB was giving cyclic error but XStream handled it successfully

Up Vert
  • 35
  • 5
  • 3
    It is customary to show how you would use it in addition to providing the reference. Please [edit] your answer to include this. – Artjom B. Mar 22 '15 at 11:19
1

just look at this tutorial : Mapping cyclic references to XML by jaxb

I use it an it works well :)

Wolfgang Fahl
  • 15,016
  • 11
  • 93
  • 186
LE GALL Benoît
  • 7,159
  • 1
  • 36
  • 48