1

I am working on a Java application using Spring Boot, and I have a UserEntity class where users can follow each other. However, when two users follow each other, I encounter a java.lang.StackOverflowError. Here is my current UserEntity class:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

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

@Entity
@Table(name = "user")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String userName;
    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "user_followings",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "following_id")
    )
    @JsonIgnoreProperties({"followers", "followings"})
    @Builder.Default
    private Set<UserEntity> followings = new HashSet<>();

    @ManyToMany(mappedBy = "followings", fetch = FetchType.EAGER)
    @JsonIgnoreProperties({"followers", "followings"})
    @Builder.Default
    private Set<UserEntity> followers = new HashSet<>();
}

Whenever two users follow each other, the application throws a java.lang.StackOverflowError. How can I prevent this error while maintaining the relationship between user and followings and followers?

What I've Tried:

  1. Using @JsonIgnoreProperties on the followings and followers fields to prevent infinite recursion.

Expected Behavior:

When I retrieve a user, I should be able to see their followers and the users they are following.

Actual Behavior:

When two users follow each other, the application crashes with a java.lang.StackOverflowError. And Also when i get all the users, i can see the followings of the each user but when i get user by username, it doesnt fetch the followings

2
  • The reason of this error may be found in stack trace. More likely it is in Json serialization. In such case these stackoverflow answers might help stackoverflow.com/questions/57768651/… stackoverflow.com/questions/3325387/… Commented Jun 9, 2024 at 9:02
  • Please provide more info. I've managed to successfully run a test case, using the setup that you are mentioning. Commented Jun 10, 2024 at 11:06

2 Answers 2

0

I think your code works well..

I wrote test code

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    public User test() {
        User user = userRepository.findById(1L).orElseThrow();
        User follower = userRepository.findById(2L).orElseThrow();

        user.getFollowings().add(follower);
        follower.getFollowers().add(user);

        follower.getFollowings().add(user);
        user.getFollowers().add(follower);

        userRepository.save(user);
        return user;
    }
}



@RestController
@AllArgsConstructor
public class TestController {
    private final UserService userService;

    @GetMapping("/test")
    public User test() {
        return userService.test();
    }
}

and i got this response

{
    "id": 1,
    "userName": "userName1",
    "password": "userPw1",
    "followings": [
        {
            "id": 2,
            "userName": "follower",
            "password": "follower"
        }
    ],
    "followers": [
        {
            "id": 2,
            "userName": "follower",
            "password": "follower"
        }
    ]
}
Sign up to request clarification or add additional context in comments.

Comments

0

To begin with, an observation.

The error occurs when there are cycles, by users following each other, as the OP noted.

I've written and tested three versions, the last of which correctly lists all followers and following for all users.

The last version appears to be essentially the same as what the OP has written. It has been tested with transitive and direct self-references.

The following two versions both work, without listing followings or followers.

The first uses annotation @JsonIgnore above each field, while the second uses annotation @JsonIgnoreProperties({"followers", "followings"}) above the class declaration.

@Entity
@Table(name = "userentity")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UserEntity {

    @Id
    private Long id;
    @Column
    private String userName;
    @Column
    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "user_followings",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "following_id")
    )
    @Builder.Default
    @EqualsAndHashCode.Exclude
    @JsonIgnore
    private Set<UserEntity> followings = new HashSet<>();

    @ManyToMany(mappedBy = "followings", fetch = FetchType.EAGER)
    @Builder.Default
    @EqualsAndHashCode.Exclude
    @JsonIgnore
    private Set<UserEntity> followers = new HashSet<>();

}

@Entity
@Table(name = "userentity")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({"followers", "followings"})
public class UserEntity {

    @Id
    private Long id;
    @Column
    private String userName;
    @Column
    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "user_followings",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "following_id")
    )
    @Builder.Default
    @EqualsAndHashCode.Exclude
    private Set<UserEntity> followings = new HashSet<>();

    @ManyToMany(mappedBy = "followings", fetch = FetchType.EAGER)
    @Builder.Default
    @EqualsAndHashCode.Exclude
    private Set<UserEntity> followers = new HashSet<>();

}

The following code works and lists the first level of followings. Note the use of @JsonIgnoreProperties({"followers", "followings"}), as the OP attempted:

@Entity
@Table(name = "userentity")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UserEntity {

    @Id
    private Long id;
    @Column
    private String userName;
    @Column
    private String password;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
            name = "user_followings",
            joinColumns = @JoinColumn(name = "id"),
            inverseJoinColumns = @JoinColumn(name = "following_id")
    )
    @Builder.Default
    @EqualsAndHashCode.Exclude
    @JsonIgnoreProperties({"followers", "followings"})
    private Set<UserEntity> followings = new HashSet<>();

    @ManyToMany(mappedBy = "followings", fetch = FetchType.LAZY)
    @Builder.Default
    @EqualsAndHashCode.Exclude
    @JsonIgnoreProperties({"followers", "followings"})
    private Set<UserEntity> followers = new HashSet<>();

}

Actual output:

[
    {
        "id": 1,
        "userName": "u1",
        "password": "pass1",
        "followings": [
            {
                "id": 3,
                "userName": "u1",
                "password": "pass"
            },
            {
                "id": 2,
                "userName": "u2",
                "password": "pass2"
            }
        ],
        "followers": [
            {
                "id": 3,
                "userName": "u1",
                "password": "pass"
            },
            {
                "id": 2,
                "userName": "u2",
                "password": "pass2"
            }
        ]
    },
    {
        "id": 2,
        "userName": "u2",
        "password": "pass2",
        "followings": [
            {
                "id": 3,
                "userName": "u1",
                "password": "pass"
            },
            {
                "id": 1,
                "userName": "u1",
                "password": "pass1"
            }
        ],
        "followers": [
            {
                "id": 3,
                "userName": "u1",
                "password": "pass"
            },
            {
                "id": 1,
                "userName": "u1",
                "password": "pass1"
            }
        ]
    },
    {
        "id": 3,
        "userName": "u1",
        "password": "pass",
        "followings": [
            {
                "id": 2,
                "userName": "u2",
                "password": "pass2"
            },
            {
                "id": 1,
                "userName": "u1",
                "password": "pass1"
            }
        ],
        "followers": [
            {
                "id": 2,
                "userName": "u2",
                "password": "pass2"
            },
            {
                "id": 1,
                "userName": "u1",
                "password": "pass1"
            }
        ]
    }
]

Note some other minor fixes to get the code working on my setup. Other issues, such as the eager loading have not been addressed.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.