Infinite Recursion in Objects with Bidirectional Relationships

In this tutorial, you will learn how to deal with an infinite recursion problem or circular reference, which occurs when you try to return an Object with Bidirectional Relationships in the HTTP response body.

Infinite Recursion Problem

Let’s look at an example that will cause an Infinite Recursion problem in our RESTful Web Service when we try to return an Object with bidirectional relationships.

Let’s assume we have a User class which contains a list of Address objects. Each Address object contains a User object. So if we attempt to return a User model class in a body HTTP Response, we will run into a problem of Infinite Recursion where we see a huge JSON representation of a User model class which contains a list of addresses. Each Address contains a User, and each User again contains a list of addresses and so on and eventually an error message.

Below are two Model classes that cause an Infinite Recursion if the framework tries to convert the Java model class into a JSON or XML representation.

User Class

import java.util.List;
import org.springframework.hateoas.ResourceSupport;
 

public class User extends ResourceSupport{

    private String userId;
    private String firstName;
    private String lastName;
    private String email;
    private String href;
    
    private List<Address> addresses;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String publicId) {
        this.userId = publicId;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getHref() {
        return href;
    }

    public void setHref(String href) {
        this.href = href;
    }

  
    public List<Address> getAddresses() {
        return addresses;
    }

   
    public void setAddresses(List<Address> addresses) {
        this.addresses = addresses;
    }

}

The above User class has an Address class. Below is a source code of an Address model.

Address Class

package com.appsdeveloperblog.app.ws.ui.model.response;

import com.fasterxml.jackson.annotation.JsonBackReference;
import org.springframework.hateoas.ResourceSupport;
 
public class Address extends ResourceSupport{
    private String city;
    private String country;
    private String streetAddress;
    private String postalCode;
    private String type;
    private String addressId;
    private User userDetails;

    public String getCity() {
        return city;
    }
 
    public void setCity(String city) {
        this.city = city;
    }

 
    public String getCountry() {
        return country;
    }
 
    public void setCountry(String country) {
        this.country = country;
    }
 
    public String getStreetAddress() {
        return streetAddress;
    }
 
    public UserRest getUserDetails() {
        return userDetails;
    }
 
    public void setUserDetails(UserRest userDetails) {
        this.userDetails = userDetails;
    }
 
    public void setStreetAddress(String streetAddress) {
        this.streetAddress = streetAddress;
    }
 
    public String getPostalCode() {
        return postalCode;
    }
 
    public void setPostalCode(String postalCode) {
        this.postalCode = postalCode;
    }
 
    public String getType() {
        return type;
    }
 
    public void setType(String type) {
        this.type = type;
    }
 
    public String getAddressId() {
        return addressId;
    }
 
    public void setAddressId(String addressId) {
        this.addressId = addressId;
    }
}

There are a few ways to solve the problem of Infinite Recursion.

Solve the Infinite Recursion problem with @JsonManagedReference, @JsonBackReference

One of the ways to solve the Infinite Recursion problem in model classes, which we return back in the body of HTTP Response, is to use the @JasonManagedReference and the @JasonBackReference annotations.

  1. Add the @JsonManagedReference annotation above the list of addresses field in the User class like so:
public class UserRest extends ResourceSupport{

    private String userId;
    private String firstName;
    private String lastName;
    private String email;
    private String href;
    
    @JsonManagedReference
    private List<Address> addresses;

   ...


}

2. Add the @JsonBackReference annotation above the User field in the Address class.

public class AddressRest extends ResourceSupport{
    private String city;
    private String country;
    private String streetAddress;
    private String postalCode;
    private String type;
    private String addressId;
    
    @JsonBackReference
    private User userDetails;

    ...

}

So if we attempt to return the User model as an Entity of our HTTP Response, the userDetails object will be skipped, and the Infinite Recursion problem will be solved. In the response, we will see the following:

{
    "userId": "3Op8ayFgsQESEkTXMLpjafRJt6f80e",
    "firstName": "Sergey",
    "lastName": "Kargopolov",
    "email": "[email protected]",
    "href": "/users/3Op8ayFgsQESEkTXMLpjafRJt6f80e",
    "addresses": [
        {
            "city": "Vancouver",
            "country": "Canada",
            "streetAddress": "123 Street name",
            "postalCode": "123456",
            "type": "shipping",
            "addressId": "8RPE6UhMou9TKPNV56CwLI2NjKoOtu"
        },
        {
            "city": "Vancouver",
            "country": "Canada",
            "streetAddress": "123 Street name",
            "postalCode": "123456",
            "type": "billing",
            "addressId": "tYmp0xb632MffV50vsEVKqKJk7Upuh"
        }
    ]
}

We can also solve the issue with Infinite Recursion using the @JsonIdentityInfo annotation.

Solve the Infinite Recursion problem with @JsonIdentityInfo Annotation

To solve the issue with circular reference, when a Java object with bidirectional relationships is converted into a JSON response, we can also use @JsonIdentityInfo annotation. I prefer to use @JsonManagedReference, and @JsonBackReference rather than @JsonIdentity. When the @JsonIdentityInfo is used, a JSON representation of the Inner object will contain a value which I think will be very confusing for developers who write applications to consume my web service.

To resolve the issue with circular reference using @JsonIdentityInfo, add it to both classes.

  1. Add @JsonIdentityInfo to a User class: 
@JsonIdentityInfo(
        generator = ObjectIdGenerators.StringIdGenerator.class,
        property="userId")
public class User extends ResourceSupport {

    private String userId;
    private String firstName;
    private String lastName;
    private String email;
    private String href;
    private List<Address> addresses;

    ....
}

2. Add @JsonIdentityInfo to an Address class: 

@JsonIdentityInfo(
        generator = ObjectIdGenerators.StringIdGenerator.class,
        property="addressId")
public class Address extends ResourceSupport {

    private String city;
    private String country;
    private String streetAddress;
    private String postalCode;
    private String type;
    private String addressId;
    private User userDetails;

   ....
}

And if we now attempt to return the User object as an Entity of our HTTP Response, the client application that calls our Web Service will get the following JSON Representation of the User object.

{
    "userId": "ZfKSw86R0IdGcEHOXp82d3N9mSkgnT",
    "firstName": "Sergey",
    "lastName": "Kargopolov",
    "email": "[email protected]",
    "href": "/users/ZfKSw86R0IdGcEHOXp82d3N9mSkgnT",
    "addresses": [
        {
            "addressId": "MoP1eg9ePsXnpWp6mqfsaQJmtuEFyU",
            "city": "Vancouver",
            "country": "Canada",
            "streetAddress": "123 Street name",
            "postalCode": "123456",
            "type": "shipping",
            "userDetails": "5926f4b8-7b32-4b33-8707-7e5159cae48f"
        },
        {
            "addressId": "mcCNj7uBlZJ9F7tuh1J7bD25iERcFH",
            "city": "Vancouver",
            "country": "Canada",
            "streetAddress": "123 Street name",
            "postalCode": "123456",
            "type": "billing",
            "userDetails": "5926f4b8-7b32-4b33-8707-7e5159cae48f"
        }
    ]
}

The circular reference is solved, and there is no Exception message thrown. Still, now, our JSON response contains the following value for the userDetails field, the presence of which I understand technically, but a user consuming such a Web Service Response will be very confused.

"userDetails": "5926f4b8-7b32-4b33-8707-7e5159cae48f"

It would be simpler to ignore or exclude the “userDetails” field from the response and not cause this confusion.  And this is exactly what we can do as another solution to a circular reference or an infinite recursion problem.

Solve the Circular Reference Problem with the @JsonIgnore

We can use the @JsonIgnore annotation to exclude the circular reference from the JSON Representation. Let’s add the @JsonIgnore annotation to an Address class to exclude the reference back to a User class.

Add the @JsonIgnore to an Address class

We can leave the User class as is with no changes:

public class User extends ResourceSupport {

    private String userId;
    private String firstName;
    private String lastName;
    private String email;
    private String href;
    private List<Address> addresses;

   ...

}

and we will annotate the userDetails field in the Address class with @JsonIgnore annotation like so:

public class Address extends ResourceSupport {

    private String city;
    private String country;
    private String streetAddress;
    private String postalCode;
    private String type;
    private String addressId;
    
    @JsonIgnore
    private User userDetails;

   ...
}

If we attempt to return the User object as a JSON Response in the body of our HTTP Response, we will get the following JSON representation:

{
    "userId": "PxpCA0aa83JBGVvsnH7dGqpCq8RuHB",
    "firstName": "Sergey",
    "lastName": "Kargopolov",
    "email": "[email protected]",
    "href": "/users/PxpCA0aa83JBGVvsnH7dGqpCq8RuHB",
    "addresses": [
        {
            "city": "Vancouver",
            "country": "Canada",
            "streetAddress": "123 Street name",
            "postalCode": "123456",
            "type": "shipping",
            "addressId": "cFwid3vxVFtY08SHpnEJ4ec3ua7qiz"
        },
        {
            "city": "Vancouver",
            "country": "Canada",
            "streetAddress": "123 Street name",
            "postalCode": "123456",
            "type": "billing",
            "addressId": "KXm3V7WfGwTrim6Tr4cO6IyM2RyOnT"
        }
    ]
}

I hope this tutorial was helpful to you! If you want to learn more about building RESTful Web Service with Spring Framework, please check the following page RESTful Web Services with Spring MVC.

And if you enjoy learning by watching video lessons, then below is a list of video courses that teach Spring MVC.


Leave a Reply

Your email address will not be published. Required fields are marked *