When building REST APIs with Spring Boot and Spring Data JPA, developers often create relationships between entities using annotations such as @OneToMany and @ManyToOne.
A common problem appears when these entities are returned as JSON responses:
- Parent entity contains a list of children.
- Child entity contains a reference back to the parent.
- Jackson tries to serialize both objects.
- Serialization never ends because each object continuously references the other.
This results in:
StackOverflowError
or extremely large JSON responses.
Let’s examine why this happens and how to solve it.
Understanding the Problem
Imagine a simple order management system.
Parent Entity
@Entity
public class Order {
@Id
private Long id;
@OneToMany(mappedBy = "order")
private List<OrderItem> items;
}
Child Entity
@Entity
public class OrderItem {
@Id
private Long id;
@ManyToOne
private Order order;
}
When an API returns an Order:
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
return orderRepository.findById(id).orElseThrow();
}
Jackson tries to serialize:
{
"id": 1,
"items": [
{
"id": 100,
"order": {
"id": 1,
"items": [
...
The process repeats forever.
Solution 1: Using @JsonManagedReference and @JsonBackReference
This is one of the simplest solutions.
Parent Side
@OneToMany(mappedBy = "order")
@JsonManagedReference
private List<OrderItem> items;
Child Side
@ManyToOne
@JsonBackReference
private Order order;
Result
Returned JSON:
{
"id": 1,
"items": [
{
"id": 100
}
]
}
The child objects are included, but the parent reference inside each child is ignored.
Advantages
- Easy implementation
- Minimal code changes
Disadvantages
- Less flexible
- Can become complicated with multiple relationships
Solution 2: Using @JsonIgnore
Sometimes the child-to-parent relationship is not needed in API responses.
Example
@ManyToOne
@JsonIgnore
private Order order;
Result
The parent reference is completely excluded.
JSON becomes:
{
"id": 1,
"items": [
{
"id": 100
}
]
}
Advantages
- Very simple
- Prevents recursion completely
Disadvantages
- Parent data cannot be returned when needed
Solution 3: Using DTO Objects (Recommended)
Most enterprise applications avoid exposing JPA entities directly.
Instead, they use Data Transfer Objects (DTOs).
Entity
@Entity
public class Order {
private Long id;
private List<OrderItem> items;
}
DTO
public class OrderDTO {
private Long id;
private List<OrderItemDTO> items;
}
Child DTO:
public class OrderItemDTO {
private Long id;
private String productName;
}
Mapping:
OrderDTO dto = mapper.toDto(order);
Advantages
- Complete control over API structure
- Better security
- Better performance
- Industry best practice
Disadvantages
- Requires mapping code
Solution 4: Using @JsonIdentityInfo
Jackson provides another option that serializes object references by ID.
Example
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
@Entity
public class Order {
}
Apply the same annotation to related entities.
Result
{
"id": 1,
"items": [
{
"id": 100,
"order": 1
}
]
}
Instead of serializing the entire object again, Jackson only writes its identifier.
Advantages
- Preserves relationships
- Avoids recursion
Disadvantages
- JSON can be less intuitive
Solution 5: Lazy Loading Considerations
Many developers encounter additional problems after fixing recursion.
Example:
@OneToMany(
mappedBy = "order",
fetch = FetchType.LAZY)
private List<OrderItem> items;
If the Hibernate session is already closed:
LazyInitializationException
may occur.
Common solutions:
- DTO mapping inside the service layer
- Fetch joins
- Entity graphs
- Open Session In View (not generally recommended)
Best Practice for Production Applications
For small projects:
@JsonManagedReference
@JsonBackReference
or
@JsonIgnore
are often sufficient.
For medium and large applications, the recommended architecture is:
Database Entity
↓
Service Layer
↓
DTO Mapping
↓
REST Response
Benefits:
- No recursion issues
- Better API design
- Improved security
- Easier maintenance
- Better versioning support
Example Architecture
Controller
↓
Service
↓
Repository
↓
Entity
Entity
↓
Mapper
↓
DTO
DTO
↓
JSON Response
This approach keeps persistence models separate from API contracts.
Conclusion
Infinite recursion in Spring Boot commonly occurs when using bidirectional @OneToMany and @ManyToOne relationships. During JSON serialization, parent and child entities continuously reference each other, causing serialization loops and potential StackOverflowError exceptions.
Several solutions exist:
| Solution | Complexity | Recommended |
|---|---|---|
| @JsonIgnore | Low | Good |
| @JsonManagedReference / @JsonBackReference | Low | Good |
| @JsonIdentityInfo | Medium | Good |
| DTO Pattern | Medium-High | Best Practice |
For enterprise-grade Spring Boot applications, DTO-based APIs are generally considered the most maintainable and scalable approach. They eliminate serialization issues while providing full control over the data exposed to clients.


