Groovy: java.lang.StackOverflowError When Implementing equals()

PROBLEM

While migrating a portion of my Java code to Groovy code, I got bitten by the Groovy operator loading feature that I should have known better… and my pride hurts, but hey, I admit I write shitty code.

Consider this simple POGO with custom equals() and hashCode(), both implemented using Google Guava libraries:-

@Canonical
class Person {
String firstName
String lastName
String email

@Override
boolean equals(Object o) {
if (this == o) {
return true
}
if (o == null || getClass() != o.getClass()) {
return false
}

final Person other = (Person) o

return Objects.equal(email, other.email)
}

@Override
int hashCode() {
return Objects.hashCode(email)
}
}

What is wrong with the above code? Well, if you are mostly a Java developer like me, this look pretty much correct. However, when I perform an equality check, I get java.lang.StackOverflowError exception. I tend to see this exception when I write my too-smart-for-production recursion API that couldn’t seem find its way to end the recursion, causing the JVM stack to blow up.

SOLUTION

The reason we are getting java.lang.StackOverflowError exception is because Groovy overloads == with equals(). So, if (this == o) { ... } becomes if (this.equals(o)) { ... }. When we perform an equality check, it will call itself again and again until it chokes itself and dies.

To fix this, we have to use if (this.is(o)) { ... } to perform an identity check:-

@Canonical
class Person {
String firstName
String lastName
String email

@Override
boolean equals(Object o) {
if (this.is(o)) {
return true
}
if (o == null || getClass() != o.getClass()) {
return false
}

final Person other = (Person) o

return Objects.equal(email, other.email)
}

@Override
int hashCode() {
return Objects.hashCode(email)
}
}