Issue
I have attempted to create a parent-child relationship of the same entity type. A problem I was facing was that when trying to do a GET request to retrieve an entity where the children are not empty, I would get a stack overflow error, from what looks like an infinite loop. After some looking around I found a fix by simply adding a @JsonIgnore to the parent field in the Entity class. My question is why does this fix that issue? What was the issue to begin with?
@Entity
@Table(name = "trash")
public class Trash {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
private String username;
@NotBlank
private String message;
private Long likes;
@ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
@JsonIgnore
private Trash parent;
@OneToMany(cascade = CascadeType.ALL, mappedBy="parent", orphanRemoval=true, fetch = FetchType.LAZY)
private Collection<Trash> children;
Here is the error I was receiving
2019-07-19 10:29:55.867 ERROR 14156 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: com.example.litter.model.Trash["parent"]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"]-
repeats for awhile....
>com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.litter.model.Trash["parent"])] with root cause
java.lang.StackOverflowError: null
Solution
The exception explains that :
Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: >com.example.litter.model.Trash["parent"]- >com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]- >org.hibernate.collection.internal.PersistentBag[0]- >com.example.litter.model.Trash["parent"]- >com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]- >org.hibernate.collection.internal.PersistentBag[0]- >com.example.litter.model.Trash["parent"]- >com.example.litter.model.Trash$HibernateProxy$lBPNIlf1["children"]- >org.hibernate.collection.internal.PersistentBag[0]- >com.example.litter.model.Trash["parent"]- and so for...
The parent
field serialization triggers the children
field serialization that itself
triggers the parent
field serialization, and so for...
That may look abstract enough, so here is a concrete example.
Suppose these three Trash
instances : one parent that has two children.
Trash-1 (children = 2,3)
Trash-2 (parent : 1)
Trash-3 (parent : 1)
Suppose you want to serialize Trash-1
(the parent).
Here is how Jackson proceeds :
1) Jackson tries to serialize Trash-1
fields, which the children
field (Trash-2
and Trash-3
) that interest us.
So it starts the children
serialization.
2) But to serialize Trash-2
, Jackson also needs to serialize its fields, which the parent
field that is Trash-1
!
3) We go back to the first step.
And it goes on until that the recursion be too important and Jackson stops you.
By annotating parent
or children
with @JsonIgnore
, you ask to Jackson to skip one of both serialization and so it breaks the recursion.
For example on parent
, it would give :
1) Jackson tries to serialize Trash-1
fields, which the children
field (Trash-2
and Trash-3
) that interest us.
So it starts the children
serialization.
2) Trash-2
is serialized (no parent
field serialization required).
2) Trash-3
is serialized (no parent
field serialization required).
And it is done.
Answered By - davidxxx
Answer Checked By - Marie Seifert (JavaFixing Admin)