Javassist Messes With Custom Object Equals Implementation

Geek10 Comments

Posting another debugging nightmare so I can hopefully spare someone the pain that I endured today…

So we have these POJOs with relatively straightforward looking overrides of the Object.equals() method. Here we go:


class Whitelist

{

private String id;

@Override

public boolean equals(final Object obj)

{

if (this == obj)

{

return true;

}

if (obj == null)

{

return false;

}

if (getClass() != obj.getClass())

{

return false;

}

final Whitelist other = Whitelist.class.cast(obj);

return id == null ? false : id.equals(other.id);

}

}

This seems to be pretty textbook. This would normally work just fine however, this class is used by Hibernate to back it. That means that some crazy stuff happens at runtime via proxying from Javassist. Usually, “it just works”. However, this equals method was failing for me today and it took some debugging to find out why.

Saving you the sleuthing, there were 2 problems.

1. if (getClass() != obj.getClass()) was returning false. I knew the objects were the same and did not expect this condition to fail. Turns out the reason was that getClass() was returning Whitelist while obj.getClass() was returning Whitelist_$$_javassist_150_ so the test failed! Crap. It wasn’t smart enough. The magic runtime proxying is substituting in another class so this test fails. Looking around, we found that isAssignableFrom() should perform the test accurately.

2. After doing #1, things were still failing. I put in a variety of debug logs and found out that other.id was null. It was not supposed to be. However, I randomly noticed in another log that other.getId() was not null. This is because of the proxy object again. It doesn’t want you going straight into member variables that are friendly. Use the getters. The class is merely a facade for the real data.

So the solution that works:


class Whitelist

{

private String id;

@Override

public boolean equals(final Object obj)

{

if (this == obj)

{

return true;

}

if (obj == null)

{

return false;

}

if (!getClass().isAssignableFrom(obj.getClass()))

{

return false;

}

final Whitelist other = Whitelist.class.cast(obj);

return id == null ? false : id.equals(other.getId());

}

}