StackOverflow - toString method when using Lombok

Lombok is very useful, is not it? I use Aggressively because it automatically generates Getter, Setter, toString method etc. which are necessary in Java but I do not want to write much. However, there are things that get caught in the process of using, but I will summarize the contents that have gathered around the beginning.

Lombok StackOverflow

If you are using Lombok, you may get StackOverflow with circular references such as when an exception occurs during processing such as the following.

 at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]    at net.beaglesoft.girafa.domain.YayoiShohizeiKubun.toString(YayoiShohizeiKubun.java:23) ~[classes/:na]    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]    at net.beaglesoft.girafa.domain.YayoiKaikeiShohizeiKubunConf.toString(YayoiKaikeiShohizeiKubunConf.java:21) ~[classes/:na]    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]    at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[na:1.8.0_60]    at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:527) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]    ...    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]    at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[na:1.8.0_60]2015-11-13 20:10:44.823 DEBUG [http-nio-8080-exec-1] o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed 2015-11-13 20:10:44.823 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 2015-11-13 20:10:44.823 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 2015-11-13 20:10:44.825 ERROR [http-nio-8080-exec-1] o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler processing failed; nested exception is java.lang.StackOverflowError] with root cause java.lang.StackOverflowError: null    at java.lang.Integer.toString(Integer.java:400) ~[na:1.8.0_60]    at java.sql.Timestamp.toString(Timestamp.java:312) ~[na:1.8.0_60]    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]    at net.beaglesoft.girafa.domain.user.User.toString(User.java:64) ~[classes/:na]    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]    at net.beaglesoft.girafa.domain.YayoiShohizeiKubun.toString(YayoiShohizeiKubun.java:23) ~[classes/:na]    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]    ...

Cause of occurrence

This is caused by circulation of the object. It occurs in the following cases as a reason.

  1. When the toString method is called with an exception message when an exception occurs, an object circulated in the corresponding object exists.
  2. Lombok automatically generates the toString method.

Specifically, the following cases apply.

First, the association Group is defined in the User class.

@Data@NoArgsConstructor@Entity@Table(name = "users")@JsonIgnoreProperties(value = {"handler", "hibernateLazyInitializer"})public class User implements Serializable {    private Group group;}

Next, in the Group class we have defined the reference of the User class as an updated user.

@Data@NoArgsConstructor@Entity@Table(name = "users")@JsonIgnoreProperties(value = {"handler", "hibernateLazyInitializer"})public class Group implements Serializable {    ...    private User updateUser;}

At this time, an exception occurs in the User class. The toString method may be called in the exception, but this time it is called.

When you execute the toString method of the User class, the toString method generated by Lombok executes the toString method of the Group class and flows.

Then the updateUser variable defined in the toString method of the Group class is called with the toString method. At this time, we also execute the toString method of the User class, then the toString method of the Group class in the User class is executed … and so on.

Solution

Well, this is a way to avoid this circular reference. If you check the method, it is OK to set the annotation on Lombok to the original class (User class) as follows.

@Data@ToString(exclude = {"group"})          //← Here specifies the field to exclude from toString.@NoArgsConstructor@Entity@Table(name = "users")@JsonIgnoreProperties(value = {"handler", "hibernateLazyInitializer"})public class User implements Serializable {    ...    private Group group;}

It is safe because it will not be StackOverflow when an exception occurs. However, there is another way to implement the toString method yourself. Here, if you do not circulate it will not cause problems.