Having a problem using Jackson to serialize your Java objects? Getting something like "Infinite recursion (StackOverflowError)?"

It's probably because you're dealing with a bidirectional relationship.

Fortunately, it's easy to fix the problem you're experiencing.

And in this guide, I'll show you how.

The Problem

First, let's take a look at the problem. Suppose you have this simple Employee class for an HR app:

public class Employee {

    private String id;    
    private String lastName;
    private String firstName;
    private List<Review> reviews;

//getters and setters

}

And here's the Review class that represents an employee's annual review:

public class Review {

    private String id;
    private String synopsis;
    private Employee employee;

//getters and setters

}

As you can see, there's a 1:many relationship between employee and review. But each review belongs to one and only one employee.

Pretty standard stuff. It's the kind of bidirectional relationship that you'll find frequently throughout applications that require any kind of object modeling.

Now try to serialize it as follows:

ObjectMapper mapper = new ObjectMapper();            

Review review = new Review();
review.setId("3A");
review.setSynopsis("Doing well");

Employee employee = new Employee();
employee.setId("A17");
employee.setFirstName("Frank");
employee.setLastName("Smith");

review.setEmployee(employee);
employee.setReviews(List.of(review));

System.err.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(employee));

That's going to give you a StackOverflowError with a very long stacktrace.

Why? Well think about what it's doing.

It's serializing the Employee object. That object includes one or more Review objects.

Then it serializes the Review object which includes an Employee object.

So Jackson goes back to the Employee object and serializes it again. But that object still includes one or more Review objects.

On and on. That process would go on forever were it not for the StackOverflowError.

So that's the problem you need to fix.

Fixing the Problem You Need to Fix

You can get around that challenge with a couple of annotations.

First up is the @JsonManagedReference annotation. That tells Jackson that the annotated property is part of a bidirectional relationship.

In this case, you'll add it to the reviews property in the Employee class like so:

public class Employee {

    private String id;    
    private String lastName;
    private String firstName;
    
    @JsonManagedReference
    private List<Review> reviews;

...

}

But you need to do some work on the other end of that bidirectional relationship as well. You'll do that with the @JsonBackReference annotation.

public class Review {

    private String id;
    private String synopsis;
    
    @JsonBackReference
    private Employee employee;

...

}

With both of those annotations, you've told Jackson not to do the infinite serialization thing.

Now try to run that serialization code again. You should get something that looks like this:

{
  "id" : "A17",
  "lastName" : "Smith",
  "firstName" : "Frank",
  "reviews" : [ {
    "id" : "3A",
    "synopsis" : "Doing well"
  } ]
}

Boom chaka laka! No stack overflow!

Something to keep in mind here: in a 1:many relationship, you'll need to put the @JsonManagedReference annotation on the "many" part. Just as you see above.

You can't use @JsonBackReference on a List.

Another Option

You could also use @JsonIgnore to ignore one side of the bidirectional relationship.

Just take out the previous annotations and update the Review class like so:

public class Review {

    private String id;
    private String synopsis;
    
    @JsonIgnore
    private Employee employee;

...

}

Rerun the serialization code and that will give you the same output as above.

Wrapping It Up

Good on ya. Now you know how to handle bidirectional relationships with Jackson.

Feel free to use what you've learned here in your own software development efforts.

Have fun!

Photo by Ave Calvar Martinez from Pexels