Skip to content

StackOverflowError Springboot OneToMany BiDirectional Mapping

I am currently coding a project, which requires me to map in between two entities: An account and a member. An account can have multiple members, whilst the member can only have one account.
After I’ve finished coding the bidirectional way, everything was working and I got the response I wanted, which is the members only.

From here on the problems started:
I started coding my way to “recipe”. Recipe is connected in a ManyToOne relationship to “house”, which then has a 1:1 relationship to “account”. After implementing this, I’ve discovered a StackOverFlow, which previously wasn’t there. (The one between Account and Member)

Below is an image of said structure in the database. Recipe -> House -> Account. (The relationships and tables, with blue crosses on top exist, but aren’t really connected) The ERM

My entities look like this:

package com.myhome.api.components.account.entity;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.myhome.api.components.house.entity.House;
import com.myhome.api.components.member.entity.Member;
import com.myhome.api.components.recipe.entity.Recipe;
import lombok.Data;

import javax.persistence.*;
import java.util.List;
import java.util.Set;

@Table(name = "account")
@Entity
@Data
public class Account {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Integer id;

    @Column(name = "email")
    private String email;

    @Column(name = "password")
    private String password;

    @Column(name = "token")
    private String token;

    @OneToMany(
            cascade = {CascadeType.ALL},
            orphanRemoval = true,
            mappedBy = "fkAccountId")
    @JsonBackReference
    private Set<Member> members;
}
package com.myhome.api.components.member.entity;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.myhome.api.components.account.entity.Account;
import com.myhome.api.components.meal.entity.Meal;
import com.myhome.api.components.rating.entity.Rating;
import lombok.Data;

import javax.persistence.*;
import java.util.Set;

@Table(name = "member")
@Entity
@Data
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "icon")
    private Integer icon;

    @ManyToOne
    @JoinColumn(name = "fkAccountId", nullable = false)
    @JsonManagedReference
    private Account fkAccountId;

    @OneToMany(
            cascade = {CascadeType.ALL},
            orphanRemoval = true,
            mappedBy = "fkMemberId")
    @JsonBackReference
    private Set<Meal> meals;

    @OneToMany(
            cascade = {CascadeType.ALL},
            orphanRemoval = true,
            mappedBy = "fkMemberId")
    @JsonBackReference
    private Set<Rating> ratings;
}

My questions are:

  1. Why is this happening, even tho it didn’t happen before I added the connection in between recipe and house?
  2. How can I fix it?
  3. What are the causes?

Answer

Why is this happening

This is caused by toString() method being present in both the classes and forming cyclic dependency. This leads to infinite recursion calls between Account and Member‘s toString() methods.

Consider these 2 simple classes. Lombok’s @Data generates toString method for all fields by default.

class Member {
    String name;
    Account account;

    @Override
    public String toString() {
        return "Member{" +
                "name='" + name + ''' +
                ", account=" + account.toString() + //Although string concatenation calls toString() on instance, I thought of adding it to call it out
                '}';
    }
}

class Account {
    String id;
    Member member;

    @Override
    public String toString() {
        return "Account{" +
                "id='" + id + ''' +
                ", member=" + member.toString() + //Although string concatenation calls toString on instance, I thought of adding it to call it out
                '}';
    }
}

public static void main(String[] args) {
        Account account = new Account();
        Member member  = new Member();
        account.id="1";
        account.member=member;

        member.name ="XYZ";
        member.account=account;

        System.out.println(account);
    }

When you call toString method on one of the classes, say Account it will in turn call toString method of Member and the cycle repeats until you run out of stack memory. Here’s the stacktracee:

Exception in thread "main" java.lang.StackOverflowError
    at java.lang.StringBuilder.append(StringBuilder.java:136)
    at com.javainuse.main.Member.toString(AnotherMain.java:23)
    at java.lang.String.valueOf(String.java:2994)
    at java.lang.StringBuilder.append(StringBuilder.java:131)
    at com.javainuse.main.Account.toString(AnotherMain.java:10)
    at java.lang.String.valueOf(String.java:2994)

How can I fix it?

Exclude toString generation for one of the classes from your lombok and that should work.refer this I would recommend not having toString at all for entity classes.

What are the causes?

I don’t think I understand. Could you elaborate?