Introduction

Jackson throws a StackOverflowError when serializing objects with circular references -- for example, a Parent object that references its Child objects, which in turn reference back to the Parent. Jackson follows these references recursively until the stack overflows. This is a fundamental problem in object-relational mapping where bidirectional relationships (@ManyToOne and @OneToMany) are common. Without proper configuration, any attempt to serialize these entities to JSON causes the application to crash.

Symptoms

bash
java.lang.StackOverflowError: null
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
    at com.example.entity.Parent$HibernateProxy$...$JacksonSerializer.serialize(Unknown Source)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)
    ... (repeating Parent -> Child -> Parent pattern)

Or with smaller objects:

bash
java.lang.StackOverflowError
    at com.fasterxml.jackson.databind.node.ObjectNode.put(ObjectNode.java:382)
    at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:810)

The error occurs when returning an entity from a REST endpoint:

java
@GetMapping("/parents/{id}")
public Parent getParent(@PathVariable Long id) {
    return parentRepository.findById(id).orElseThrow();
    // StackOverflowError when Jackson tries to serialize
}

Common Causes

  • Bidirectional JPA relationships: @OneToMany on parent and @ManyToOne on child create a cycle
  • Parent-child tree structures: TreeNode objects referencing their parent node
  • Graph data structures: Node objects with edges pointing to other nodes that point back
  • Self-referencing entities: Employee references Manager who is also an Employee
  • Hibernate proxies adding extra nesting: Lazy-loaded proxies add serialization complexity
  • DTO with back-reference: DTOs that accidentally replicate the entity's circular structure

Step-by-Step Fix

Step 1: Use @JsonManagedReference and @JsonBackReference

```java @Entity public class Parent { @Id private Long id; private String name;

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) @JsonManagedReference // This side is serialized private List<Child> children = new ArrayList<>(); }

@Entity public class Child { @Id private Long id; private String name;

@ManyToOne @JoinColumn(name = "parent_id") @JsonBackReference // This side is NOT serialized (breaks the cycle) private Parent parent; } ```

The serialized output:

json
{
  "id": 1,
  "name": "Parent A",
  "children": [
    {"id": 10, "name": "Child 1"},
    {"id": 11, "name": "Child 2"}
  ]
}

Step 2: Use @JsonIdentityInfo for graph serialization

When you need both sides serialized but without infinite recursion:

```java @Entity @JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id" ) public class Department { @Id private Long id; private String name;

@OneToMany(mappedBy = "department") private List<Employee> employees = new ArrayList<>(); }

@Entity @JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id" ) public class Employee { @Id private Long id; private String name;

@ManyToOne private Department department; } ```

Output:

json
{
  "id": 1,
  "name": "Engineering",
  "employees": [
    {"id": 10, "name": "Alice", "department": 1},
    {"id": 11, "name": "Bob", "department": 1}
  ]
}

Step 3: Use DTOs (recommended for REST APIs)

```java public record ParentDTO( Long id, String name, List<ChildDTO> children ) { public static ParentDTO fromEntity(Parent parent) { return new ParentDTO( parent.getId(), parent.getName(), parent.getChildren().stream() .map(ChildDTO::fromEntity) .toList() ); } }

public record ChildDTO(Long id, String name) { public static ChildDTO fromEntity(Child child) { return new ChildDTO(child.getId(), child.getName()); // Note: no parent reference in DTO } } ```

Prevention

  • Never return JPA entities directly from REST controllers -- use DTOs
  • If using entities directly, always add @JsonManagedReference/@JsonBackReference to bidirectional relationships
  • Use @JsonIgnore on one side of the relationship as a quick fix
  • Add @JsonIdentityInfo for graph-like data where references are needed
  • Configure SerializationFeature.FAIL_ON_SELF_REFERENCES to catch cycles early
  • Add integration tests that serialize every entity returned by your REST endpoints