I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE COURSE

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.

I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE LESSONS

Sort by:   newest | oldest | most voted
Grant Walker
Guest
Thanks for the post. Whats the best option to go with when you have a huge bidirectional ERD. In my project Im trying to Serialize a User (for eg), but it looks kinda like this: User – has a Company – Company has a User (infinite loop) -Have Appointments – Apts have a Type – Types have a list of Appointments (infinite loop) – Apts have the original User (infinite loop) It goes on with at least a dozen other objects all interlinked. Id like to keep my code as clean and readable as possible, so I think the @JsonIdentityInfo… Read more »
Grzegorz Piwowarek
Guest

Grant, nothing is wrong with having such a structure. In order to avoid problems with recursive serialization/deserialization, you should check @JsonManagedReference and @JsonBackReference annotations. I hope it helps

Grant Walker
Guest

Thanks for the quick response, so I’ve added a @JsonManagedReference to the Company attribute in User, and @JsonBackReference to the User attribute in Company.
This works great for retrieving a list of Users (company is populated, without the original user), but when I retrieve a list of Companies now, the Back Ref prevents the Users list from serializing. Is there a way to ‘manage it’ from both sides?

On a side bar, is there a way to prevent serialization of all collections in an object (without using a custom serializer)?

Grzegorz Piwowarek
Guest

Well, it’s a tricky situation and honestly I do not know 🙂 Let me know if you find out the answer for this one.

I do not think that there exists a tool that could allow disabling serialization of all collections but surely you can use @JsonIgnore on all of them manually

Grant Walker
Guest
Hi, I’ve moved onto using the @JsonManagedReference & @JsonBackReference annotations, and need to know, how do I handle multiple managed and back reference annotation in a single class. Ive set matching values on the managed/back ref pairs but my requests are still failing with a 415 error. my server console prints: Failed to evaluate Jackson deserialization for type [[simple type, class za.co.itdynamics.planner.domain.CompanyUser]]: com.fasterxml.jackson.databind.JsonMappingException: Multiple back-reference properties with name ‘defaultReference’ 2016-11-21 14:11:16.387 WARN 9436 — [nio-8080-exec-8] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class za.co.itdynamics.planner.domain.CompanyUser]]: com.fasterxml.jackson.databind.JsonMappingException: Multiple back-reference properties with name ‘defaultReference’ 2016-11-21 14:11:16.428 WARN 9436 —… Read more »
Grzegorz Piwowarek
Guest

If you have multiple @JsonManagedReference and @JsonBackReference, you could try naming them explicitly. They both accept String parameters so you could try helping them match by giving your properties a name like:

@JsonBackReference(value=”property1″)
@JsonManagedReference(value=”property1″)

Of course, use more meaningful names 🙂 Let me know if that idea helped 🙂

Grant Walker
Guest

They are named, both the managed and back reference pair are the same.
I have two Managed References that are objects and two back references that are Sets

Grzegorz Piwowarek
Guest

Can you show the actual code? because this is how you are supposed to resolve this kind of problems but without seeing an actual code it’s just a wild guessing

Grant Walker
Guest
Sure, I’ve only included the referenced fields for brevity: public class CompanyUser{ @ManyToOne @JsonManagedReference(value = “companyUser-user”) private User user; @ManyToOne @JsonManagedReference(value = “companyUser-company”) private Company company; @OneToMany(mappedBy = “companyUser”) @JsonBackReference(value = “appointment-companyUser”) private Set appointments; @OneToMany(mappedBy = “costCentreCustodian”) @JsonBackReference(value = “salesAgencyCostCentres-companyUser”) private Set salesAgencyCostCentres; } public class Company { @OneToMany(mappedBy = “company”) @JsonBackReference(value = “companyUser-company”) private Set companyUsers; } public class User { @OneToMany(mappedBy = “user”) @JsonBackReference(value = “companyUser-user”) private Set companyUsers; } public class Appointment { @ManyToOne @JsonManagedReference(value = “appointment-companyUser”) private CompanyUser companyUser; } public class SalesAgencyCostCentre { @ManyToOne @JsonManagedReference(value = “salesAgencyCostCentres-companyUser”) private CompanyUser companyUser; } Here is my… Read more »
Grzegorz Piwowarek
Guest

Another question, why are your collections parameterized with a types whose names start with a lowercase? Or is this a mistake?

Grant Walker
Guest

I didnt notice, Its a mistake. Its not like that when i edit the post, its must be some kind of markup in the forum.

Grant Walker
Guest

@grzegorz_piwowarek:disqus Have you had any luck yet?

Grzegorz Piwowarek
Guest

I am speaking today on a conference in Kiev and I am pretty busy so far

Grzegorz Piwowarek
Guest

Well, I do not really know the answer right away. What I would suggest you to do, is to start a new thread at Stackoverflow with a complete example and post a link here. If I do not find an answer, someone else surely will 🙂

Victor Kalinin
Guest

Hello,
Can’t understand in case with @JsonIdentity info, what is ‘”userItems”:[2]’? Where it comes from? Is there any specification Jackson/Rest/.. describing this?

Jason Glez
Guest

You can also use @JsonIgnoreProperties({ “parameter1″,”parameter2” }) for example:

public class User {
public int id;
public String name;
@JsonIgnoreProperties({ “owner” })
public List userItems;
}
public class Item {
public int id;
public String itemName;
@JsonIgnoreProperties({ “userItems” })
public User owner;
}

Grzegorz Piwowarek
Guest

But in this case, you are simply ignoring fields and the tricky is part is when you actually can’t simply ignore them

Jason Glez
Guest

But I just ignore the circular reference field, it’s really not used at all

Grzegorz Piwowarek
Guest

Well, if you do not need it, then it’s never problematic. Probably the best option would be to separate domain classes from REST responses. A new DTO class would simply not have an unnecessary field

Jason Glez
Guest

In my case I had problems getting the entities from one to many, and many to one, because in many to one with the other implementations the object is removed

Sagar Kapadia
Guest
Hi! I have a problem with array deserialization when using JsonIdentityInfo. The serialization takes place correctly and the array contains a few Ids where there are cyclic references. However, I cannot figure out how to deserialize the ids into objects. I get an array with some objects and some strings. [I use UUIDs for ids @JsonIdentityInfo( generator=ObjectIdGenerators.UUIDGenerator.class, property=”_lineItemExternalId”, scope=LineItemExternal.class ) The array is serialized as // edited out Here, “cee9d79b-77a9-4b3b-a376-ead1d6347d03”, and “a15661e1-b4d4-4145-8db8-4e66ad0e4f81” are LineItemExternal ids, which have been serialized completely in the above json. [Removed for brevity] The code which throws the error is // edited out It throws ClassCastException… Read more »
Grzegorz Piwowarek
Guest

Sagar, it’s super hard to debug such code by using eyes only. If you could prepare a PR with a failing test case, that would make it much easier to investigate

Sagar Kapadia
Guest

How do I upload the test project?

Grzegorz Piwowarek
Guest
Sagar Kapadia
Guest

Grzegorz,
Thanks for the prompt reply.
The repository is at

https://github.com/ks1974in/DeserializationTestForJson.git

The sample input json is in file input.json in the project folder. The test case is in package in.cloudnine.inventory.test;
It is TestSerializationAndDeserialization
The failing test is testWithFile()

Sorry for including so much code. But the previous tests with limited code ALL SUCCEEDED. However, the above test does fail with a class cast exception

Thanks,
Sagar

Grzegorz Piwowarek
Guest

Sagar, you are storing there objects of different types. During the first loop run, it deserializes everything correctly and during a second it does not receive a proper object and gets a “c85f6ee8-4f47-4210-9d33-6654a0b2ac3d” instead. This is why you get an exception because it can’t deserialize a String into LineItemExternal

Sagar Kapadia
Guest

Yes, i know that. But that is the result of serialization. Those are IDs of line items previously encountered by Jackson. If you uncomment the log statement in other tests,(testArrays()) you will see IDs in the other JSON too. But in other tests there are no problems.
Thanks

Sagar Kapadia
Guest

Any thoughts on this? should I file a bug report?

Rodrigo Estevao Rodrigues
Guest
Rodrigo Estevao Rodrigues

Excellent post! Thank you a lot!