Issue
I'm having a strange problem with a spring boot endpoint that calls JpaRepository findById(). When ever I send a GET request to the endpoint /v1/goals/{id} a stack overflow error occurs, while GET requests to /v1/goals work fine.
Edit: added the error message to the bottom
Simplified Controller class:
@RestController
public class GoalController {
private final GoalServiceImpl service;
@Autowired
GoalController(GoalServiceImpl service) { this.service = service; }
@GetMapping("/v1/goals")
ResponseEntity<List<Goal>> allGoals() { return new ResponseEntity<>(service.getGoals(), HttpStatus.OK); }
@GetMapping("/v1/goals/{id}")
ResponseEntity<String> singleGoal(@PathVariable Long id) {
return new ResponseEntity<>(service.getGoalById(id).toString(), HttpStatus.OK);
}
}
Simplified Service class:
@Service
public class GoalServiceImpl implements GoalService {
private final GoalRepository repository;
@Autowired
public GoalServiceImpl(GoalRepository repository) { this.repository = repository; }
public Goal getGoalById(Long id) {
return repository
.findById(id)
.orElseThrow(() -> new GoalNotFoundException(id));
}
public List<Goal> getGoals() { return repository.findAll(); }
}
Simplified Entity class:
@NoArgsConstructor
@Data
@Entity
public class Goal {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long goalId;
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_id")
@JsonIgnore
private User user;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "goal")
@JsonIgnoreProperties(value = "goal")
private List<Milestone> milestones;
}
Simplified Milestone Entity:
@NoArgsConstructor
@Data
@Entity
public class Milestone {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long milestone_id;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "user_id")
@JsonIgnore
private User user;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "goal_id")
private Goal goal;
Simplified User Entity:
@NoArgsConstructor
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long userId;
private String name;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
@JsonIgnoreProperties(value = "user")
private List<Goal> goals;
}
Error message:
java.lang.StackOverflowError: null
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:56) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at com.motivate.api.user.User$HibernateProxy$KaIclPZ9.toString(Unknown Source) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:2951) ~[na:na]
at com.motivate.api.goal.Goal.toString(Goal.java:16) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:2951) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:168) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:473) ~[na:na]
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at java.base/java.lang.String.valueOf(String.java:2951) ~[na:na]
at com.motivate.api.user.User.toString(User.java:11) ~[classes/:na]
at jdk.internal.reflect.GeneratedMethodAccessor58.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:56) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at com.motivate.api.user.User$HibernateProxy$KaIclPZ9.toString(Unknown Source) ~[classes/:na]
Solution
Solution
provided by Chris and PaulD in the comments:
@GetMapping("/v1/goals/{id}")
ResponseEntity<Goal> singleGoal(@PathVariable Long id) {
return new ResponseEntity<>(service.getGoalById(id), HttpStatus.OK);
}
Remove the toString() as that causes the whole object to be serialised by lombok which doesn't take into account the jackson annotations. Change ResponseEntity to be of type Goal and pass in the whole entity.
Answered By - Adafe Jaja
Answer Checked By - David Marino (JavaFixing Volunteer)