I just released Module 10 in the Master Class of “REST with Spring”:

>> THE "REST WITH SPRING" CLASSES

1. Overview

In this tutorial, we’ll go over the best ways to deal with bidirectional relationships in Jackson.

We’ll discuss the Jackson JSON infinite recursion problem, then – we’ll see how to serialize entities with bidirectional relationships and finally – we will deserialize them.

2. Infinite Recursion

First – let’s take a look at the Jackson infinite recursion problem. In the following example we have two entities – “User” and “Item” – with a simple one-to-many relationship:

The “User” entity:

public class User {
    public int id;
    public String name;
    public List<Item> userItems;
}

The “Item” entity:

public class Item {
    public int id;
    public String itemName;
    public User owner;
}

When we try to serialize an instance of “Item“, Jackson will throw a JsonMappingException exception:

@Test(expected = JsonMappingException.class)
public void givenBidirectionRelation_whenSerializing_thenException()
  throws JsonProcessingException {
 
    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    new ObjectMapper().writeValueAsString(item);
}

The full exception is:

com.fasterxml.jackson.databind.JsonMappingException:
Infinite recursion (StackOverflowError) 
(through reference chain: 
org.baeldung.jackson.bidirection.Item["owner"]
->org.baeldung.jackson.bidirection.User["userItems"]
->java.util.ArrayList[0]
->org.baeldung.jackson.bidirection.Item["owner"]
->…..

Let’s see, over the course of the next few sections – how to solve this problem.

3. Use @JsonManagedReference, @JsonBackReference

First, let’s annotate the relationship with @JsonManagedReference, @JsonBackReference to allow Jackson to better handle the relation:

Here’s the “User” entity:

public class User {
    public int id;
    public String name;

    @JsonBackReference
    public List<Item> userItems;
}

And the “Item“:

public class Item {
    public int id;
    public String itemName;

    @JsonManagedReference
    public User owner;
}

Let’s now test out the new entities:

@Test
public void 
  givenBidirectionRelation_whenUsingJacksonReferenceAnnotation_thenCorrect()
  throws JsonProcessingException {
 
    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    String result = new ObjectMapper().writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("John"));
    assertThat(result, not(containsString("userItems")));
}

Here is the output of serialization:

{
 "id":2,
 "itemName":"book",
 "owner":
    {
        "id":1,
        "name":"John"
    }
}

Note that:

  • @JsonManagedReference is the forward part of reference – the one that gets serialized normally.
  • @JsonBackReference is the back part of reference – it will be omitted from serialization.

4. Use @JsonIdentityInfo

Now – let’s see how to help with the serialization of entities with bidirectional relationship using @JsonIdentityInfo.

We add the class level annotation to our “User” entity:

@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class, 
  property = "id")
public class User { ... }

And to the “Item” entity:

@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class, 
  property = "id")
public class Item { ... }

Time for the test:

@Test
public void givenBidirectionRelation_whenUsingJsonIdentityInfo_thenCorrect()
  throws JsonProcessingException {
 
    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    String result = new ObjectMapper().writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("John"));
    assertThat(result, containsString("userItems"));
}

Here is the output of serialization:

{
 "id":2,
 "itemName":"book",
 "owner":
    {
        "id":1,
        "name":"John",
        "userItems":[2]
    }
}

5. Use @JsonIgnore

Alternatively, we can also use the @JsonIgnore annotation to simply ignore one of the sides of the relationship, thus breaking the chain.

In the following example – we will prevent the infinite recursion by ignoring the “User” property “userItems” from serialization:

Here is “User” entity:

public class User {
    public int id;
    public String name;

    @JsonIgnore
    public List<Item> userItems;
}

And here is our test:

@Test
public void givenBidirectionRelation_whenUsingJsonIgnore_thenCorrect()
  throws JsonProcessingException {
 
    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    String result = new ObjectMapper().writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("John"));
    assertThat(result, not(containsString("userItems")));
}

And here is the output of serialization:

{
 "id":2,
 "itemName":"book",
 "owner":
    {
        "id":1,
        "name":"John"
    }
}

6. Use @JsonView

We can also use the newer @JsonView annotation to exclude one side of the relationship.

In the following example – we use two JSON Views – Public and Internal where Internal extends Public:

public class Views {
    public static class Public {}

    public static class Internal extends Public {}
}

We’ll include all User and Item fields in the Public View – except the User field userItems which will be included in the Internal View:

Here is our entity “User“:

public class User {
    @JsonView(Views.Public.class)
    public int id;

    @JsonView(Views.Public.class)
    public String name;

    @JsonView(Views.Internal.class)
    public List<Item> userItems;
}

And here is our entity “Item“:

public class Item {
    @JsonView(Views.Public.class)
    public int id;

    @JsonView(Views.Public.class)
    public String itemName;

    @JsonView(Views.Public.class)
    public User owner;
}

When we serialize using the Public view, it works correctly – because we excluded userItems from being serialized:

@Test
public void givenBidirectionRelation_whenUsingPublicJsonView_thenCorrect() 
  throws JsonProcessingException {
 
    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    String result = new ObjectMapper().writerWithView(Views.Public.class)
      .writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("John"));
    assertThat(result, not(containsString("userItems")));
}

But If we serialize using an Internal view, JsonMappingException is thrown because all the fields are included:

@Test(expected = JsonMappingException.class)
public void givenBidirectionRelation_whenUsingInternalJsonView_thenException()
  throws JsonProcessingException {
 
    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    new ObjectMapper()
      .writerWithView(Views.Internal.class)
      .writeValueAsString(item);
}

7. Use a Custom Serializer

Next – let’s see how to serialize entities with bidirectional relationship using a custom serializer.

In the following example – we will use a custom serializer to serialize the “User” property “userItems“:

Here’s the “User” entity:

public class User {
    public int id;
    public String name;

    @JsonSerialize(using = CustomListSerializer.class)
    public List<Item> userItems;
}

And here is the “CustomListSerializer“:

public class CustomListSerializer extends StdSerializer<List<Item>>{

   public CustomListSerializer() {
        this(null);
    }

    public CustomListSerializer(Class<List> t) {
        super(t);
    }

    @Override
    public void serialize(
      List<Item> items, 
      JsonGenerator generator, 
      SerializerProvider provider) 
      throws IOException, JsonProcessingException {
        
        List<Integer> ids = new ArrayList<>();
        for (Item item : items) {
            ids.add(item.id);
        }
        generator.writeObject(ids);
    }
}

Let’s now test out the serializer and see the right kind of output being produced:

@Test
public void givenBidirectionRelation_whenUsingCustomSerializer_thenCorrect()
  throws JsonProcessingException {
    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    String result = new ObjectMapper().writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("John"));
    assertThat(result, containsString("userItems"));
}

And the final output of the serialization with the custom serializer:

{
 "id":2,
 "itemName":"book",
 "owner":
    {
        "id":1,
        "name":"John",
        "userItems":[2]
    }
}

8. Deserialize with @JsonIdentityInfo

Now – let’s see how to deserialize entities with bidirectional relationship using @JsonIdentityInfo.

Here is the “User” entity:

@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class, 
  property = "id")
public class User { ... }

And the “Item” entity:

@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class, 
  property = "id")
public class Item { ... }

Let’s now write a quick test – starting with some manual JSON data we want to parse and finishing with the correctly constructed entity:

@Test
public void givenBidirectionRelation_whenDeserializingWithIdentity_thenCorrect() 
  throws JsonProcessingException, IOException {
    String json = 
      "{\"id\":2,\"itemName\":\"book\",\"owner\":{\"id\":1,\"name\":\"John\",\"userItems\":[2]}}";

    ItemWithIdentity item
      = new ObjectMapper().readerFor(ItemWithIdentity.class).readValue(json);
    
    assertEquals(2, item.id);
    assertEquals("book", item.itemName);
    assertEquals("John", item.owner.name);
}

9. Use Custom Deserializer

Finally, let’s deserialize the entities with bidirectional relationship using a custom deserializer.

In the following example – we will use custom deserializer to parse the “User” property “userItems“:

Here’s “User” entity:

public class User {
    public int id;
    public String name;

    @JsonDeserialize(using = CustomListDeserializer.class)
    public List<Item> userItems;
}

And here is our “CustomListDeserializer“:

public class CustomListDeserializer extends StdDeserializer<List<Item>>{

    public CustomListDeserializer() {
        this(null);
    }

    public CustomListDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public List<Item> deserialize(
      JsonParser jsonparser, 
      DeserializationContext context) 
      throws IOException, JsonProcessingException {
        
        return new ArrayList<>();
    }
}

And the simple test:

@Test
public void givenBidirectionRelation_whenUsingCustomDeserializer_thenCorrect()
  throws JsonProcessingException, IOException {
    String json = 
      "{\"id\":2,\"itemName\":\"book\",\"owner\":{\"id\":1,\"name\":\"John\",\"userItems\":[2]}}";

    Item item = new ObjectMapper().readerFor(Item.class).readValue(json);
 
    assertEquals(2, item.id);
    assertEquals("book", item.itemName);
    assertEquals("John", item.owner.name);
}

10. Conclusion

In this tutorial, we illustrated how to serialize/deserialize entities with bidirectional relationships using Jackson.

The implementation of all these examples and code snippets can be found in our GitHub project – this is a Maven-based project, so it should be easy to import and run as it is.

Go deeper into building a REST API with Spring:

>> CHECK OUT THE COURSE

Sort by:   newest | oldest | most voted
Davi Alves
Guest
You can also use @JsonView. In my case I had a mapping like this: public class ParentDTO { private Long id; private String name; @JsonView(View.ParentWithChild.class) private List childs; } public class ChildDTO { private Long id; private String name; private String description; @JsonView(View.ParentWithoutChild.class) private ParentDTO parent; } And on my controller two different methods, one that had to return the ParentDTO with the list of Child’s but without Parent, and one that return the Child’s and had to return it’s Parent as well, but the Parent shouldn’t include the Child otherwise it would lead to a StackOverflow error. Worked like… Read more »
Eugen Paraschiv
Guest

First, sorry about the late response – I saw the comment, integrated @JsonView, but I must have missed actually responding. So yes – that solution is now included.
Finally – thanks for the properly formatter code. I have been using Disqus for around 3 years now and have over a thousand comments, and this is the first one with correctly formatted code 🙂
Cheers,
Eugen.

Erik Gollot
Guest

Why don’t use an annotation like jpa with opposite attribute ?
What happen if we’ve two relationships ?

Eugen Paraschiv
Guest

Hey Erik – if you have a bidirectional relationship – without special configuration, Jackson will simply keep processing the graph and end up with a StackOverflow, as a result of the loop. Cheers,
Eugen.

Erik Gollot
Guest

The objective of the presented solution is to deal with bidirectional relationships.
But the solution make me feel that it works only if you’ve only one relationship

Davi Alves
Guest

Check the @JsonView solution, it has a bidirectional relationship.

Eugen Paraschiv
Guest

Actually, all of these solutions target bi-directional relationships. It’s funny that Jackson actually has so many ways to do this – but it does, so pick the one that fits your use-case best and go with it. Cheers,
Eugen.

Muthu Raj
Guest

I have a similar strutcture but it fails with the infinite error when I try to get the data
Parent class
public List getUnitDetails() {
return unitDetails;
}

Child Class
public Project getProject() {
return project;
}

I have also annotated both the classes as below
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = “id”)

Anyone help

Eugen Paraschiv
Guest

Hey Mathu,
First – the persistence annotations have nothing to do with Jackson, so you can remove them from the code example (to make it clearer). Second – since this is very code-focused, go ahead and open an issue over on Github with a full test example that’s runnable and I’d be happy to have a look. Cheers,
Eugen.

Muthu Raj
Guest
Hi I removed the annotations as suggested. FYI, This is my Jquery method function getProjectDetails() { var userID = localStorage.getItem(‘userID’); var request = $.ajax({ url : ‘/ABC/project.json’, data : { userID : userID }, //dataType : ‘json’, type : ‘GET’, contentType : ‘application/json’, async : true }); Here is the exception The server encountered an internal error that prevented it from fulfilling this request.exceptionnet.sf.json.JSONException: There is a cycle in the hierarchy!
Jodi Depp
Guest

what if all the entities are configured with the xml, What I can do? Is there any config for hbm.xml ?

Eugen Paraschiv
Guest

Hey Jodi,
So, this is primarily about Jackson, not Hibernate, so I’m not sure how the hbm.xml comes into it. Can you elaborate? Cheers,
Eugen.

Jodi Depp
Guest

Hey Eugen,
I meant that I configured the enteties with hbm.xml, not anotations.
Anyway I solved my problem with the help of @JsonIdentityInfo 🙂 Thanks
Cheers )

Eugen Paraschiv
Guest

Glad it’s sorted out. Cheers,
Eugen.

Edwin Quai Hoi
Guest
Hi first of all nice post, but I have come across an edge case that I was hoping you could help with. It is as follows, I have 3 entities A,B and C respectively. A has a many to many relationship with both B and C, while B and C have a many to many relationship with each other. This creates a cycle from A -> B -> C -> A. Each object has a JsonIdentity annotation. Now when I go to serialize A where it is linked to B and C and B and C are linked to each… Read more »
Eugen Paraschiv
Guest

Hey Edwin,
That’s an interesting question, but it’s unfortunately going to be difficult to answer without looking at the code. The way to go here would be a PR on Github with a failing test – let me know and I’d be happy to have a look.
One quick note is that using annotation usually works up until a certain complexity – once you’re past that you can have a look at custom serializers and have more control over the process.
Hope that helps. Cheers,
Eugen.

Edwin Quai Hoi
Guest

Hi Eugen,

Thanks for the prompt response here is a link to source code that provides an example. I have created a JUnit test to demonstrate the issue. Further investigation tells me that the issue occurs when de-serializing.

https://github.com/edwinquaihoi/jsonidentitymanytomany

Eugen Paraschiv
Guest

Hey Edwin,
First, thanks for the link – had a look and opened an issue on your example repo with a few notes.
Hope that helps. Cheers,
Eugen.

Edwin Quai Hoi
Guest

Hi Eugen,

Thanks for responding, I have posted code at for you to have a look at. In the meantime I have been experimenting with JSOG to see if it can provide a solution.

Link to git repo -> https://github.com/edwinquaihoi/jsonidentitymanytomany

Eugen Paraschiv
Guest

Sure thing, happy to help. Is this new code or the same one I looked at earlier (looks like it’s the same repo)? Sure, Jackson isn’t the only way to go, but when it comes to complex objects graphs, it’s one of the more flexible options.

Edwin Quai Hoi
Guest

Sorry dude, I double posted thinking my first post didn’t go through :(.

Eugen Paraschiv
Guest

No worries. It did, but it was pending because it contained a link 🙂

wpDiscuz