Preventing Infinite Recursion in Spring Boot OneToMany Relationships

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:

SolutionComplexityRecommended
@JsonIgnoreLowGood
@JsonManagedReference / @JsonBackReferenceLowGood
@JsonIdentityInfoMediumGood
DTO PatternMedium-HighBest 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.

This article is inspired by real-world challenges we tackle in our projects. If you're looking for expert solutions or need a team to bring your idea to life,

Let's talk!

    Please fill your details, and we will contact you back

      Please fill your details, and we will contact you back