How to Solve Spring Boot Bidirectional JSON Recursion with 6 Jackson Techniques
This article explains why bidirectional entity relationships cause infinite JSON recursion in Spring Boot, demonstrates the problem with sample code and console output, and presents six practical Jackson solutions—including @JsonManagedReference, @JsonIdentityInfo, @JsonIgnore, @JsonView, and custom serializers—complete with code snippets and result screenshots.
Introduction
In Spring Boot development, handling bidirectional relationships such as a one‑to‑many association between Customer and Order often leads to infinite recursion when Jackson serializes the objects to JSON, resulting in a "Recursion depth exceeded" error.
Problem Demonstration
Entity definitions:
public class Customer {
private Long id;
private String name;
private List<Order> orders = new ArrayList<>();
}
public class Order {
private Long id;
private String sno;
private BigDecimal total;
private Customer customer;
}Controller returning a Customer with populated orders:
@GetMapping("")
public ResponseEntity<?> query() {
Customer customer = new Customer();
customer.setId(1L);
customer.setName("Pack");
customer.setOrders(List.of(
new Order("S-001", BigDecimal.valueOf(100), customer),
new Order("S-002", BigDecimal.valueOf(200), customer),
new Order("S-003", BigDecimal.valueOf(300), customer)
));
return ResponseEntity.ok(customer);
}Calling this endpoint produces a page filled with repeated JSON fragments and a console warning about exceeding the maximum recursion depth of 1000.
Solution 1: @JsonManagedReference / @JsonBackReference
Mark the parent side with @JsonManagedReference and the child side with @JsonBackReference to break the cycle during serialization.
public class Customer {
@JsonManagedReference
private List<Order> orders = new ArrayList<>();
}
public class Order {
@JsonBackReference
private Customer customer;
}After applying the annotations, the endpoint returns the expected JSON where the Customer field inside Order is omitted.
The annotations can be swapped, producing the same effect.
public class Customer {
@JsonBackReference
private List<Order> orders = new ArrayList<>();
}
public class Order {
@JsonManagedReference
private Customer customer;
}Solution 2: @JsonIdentityInfo
Use @JsonIdentityInfo to serialize objects by a unique identifier, preventing repeated serialization of the same instance.
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Customer {
private Long id;
private List<Order> orders = new ArrayList<>();
}
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Order {
private Long id;
private Customer customer;
}The resulting JSON shows only the IDs for linked objects.
Solution 3: @JsonIgnore
Apply @JsonIgnore to the field that should be excluded from both serialization and deserialization.
public class Order {
@JsonIgnore
private Customer customer;
}After adding the annotation, the JSON output no longer contains the back‑reference.
Solution 4: @JsonView
Define view classes to control which fields are serialized for different API scenarios.
public class Views {
public static class Public {}
public static class Internal extends Public {}
}
public class Customer {
@JsonView(Views.Public.class)
private Long id;
@JsonView(Views.Public.class)
private String name;
@JsonView(Views.Internal.class)
private List<Order> orders = new ArrayList<>();
}
public class Order {
@JsonView(Views.Public.class)
private Long id;
@JsonView(Views.Public.class)
private String sno;
@JsonView(Views.Public.class)
private BigDecimal total;
@JsonView(Views.Public.class)
private Customer customer;
}Annotate controller methods with @JsonView(Views.Public.class) to return only the public fields.
Solution 5: Custom Serializer
Implement a custom serializer for the collection to output only the IDs of the related Order objects.
public class CustomListSerializer extends StdSerializer<List<Order>> {
public CustomListSerializer() { this(null); }
public CustomListSerializer(Class<List<Order>> t) { super(t); }
@Override
public void serialize(List<Order> orders, JsonGenerator gen, SerializerProvider provider) throws IOException {
List<Long> ids = new ArrayList<>();
for (Order order : orders) {
ids.add(order.getId());
}
gen.writeObject(ids);
}
}Apply it to the orders field:
public class Customer {
@JsonSerialize(using = CustomListSerializer.class)
private List<Order> orders = new ArrayList<>();
}The endpoint now returns a list of order IDs instead of full objects.
Conclusion
These six Jackson-based techniques—@JsonManagedReference/@JsonBackReference, @JsonIdentityInfo, @JsonIgnore, @JsonView, and custom serializers—provide flexible ways to prevent infinite recursion when serializing bidirectional relationships in Spring Boot applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
