Caveat Container
I said that I'd blog about a couple of painful lessons learned and that's a bit overdue. Sorry, I went to Holland for a week! However, here's one of those lessons learned...
If you work with containers in Java (collection classes), you'll find that they have a contains(obj) method. The idea is that you can quickly check whether a given object is already in the collection by saying:
if (myCollection.contains(myObject)) ...
This happened to me. My unit tests all passed but they had no disconnected objects. The real application began to fail and it took me a while to realize why.
For me, objects are "equal" if their id properties are equal (and they are the same type of course). In my Groovy domain objects, I define equals() to specify this behavior and Groovy conveniently calls equals() when I say objA == objB. Operator overloading is very useful (and easy) in Groovy and it can help make your code much more readable.
Unfortunately (for me), Java's collection classes don't respect that when contains() is called.
Fortunately, Groovy provides a fairly simple workaround. You can use the find() method and a closure to specify the filter. find() returns the first matching object - or null if no objects match. myCollection.contains(myObject) can therefore be replaced with myCollection.find { it == myObject } or, if you want to be explicit, myCollection.find { obj -> obj == myObject }
Since CFML does not allow you to compare objects directly, you're always going to have to write your own equality method. That means that any collection-based operations you do must explicitly call the appropriate equality method so, hopefully, you won't fall foul of Java's very literal "equality" test for the contains() method. If you work across both Java and CFML tho', you'll need to bear this in mind.

However, that's a very narrow view of equality and it doesn't work well in a lot of situations.
It's also worth pointing out that Collection.contains() is supposed to rely on equals() - which didn't seem to be the case for my code.
And then I found this comment (about equals() and hashCode() usage):
"Some of the code shipped with the standard Java libraries gets it wrong."
*groan*
So did you in fact override hashCode()? Given the contract for hashCode(), contains() is quite within its rights to check the hashcode first, then only proceed to calling equals() if the hashcodes are the same.
Either way, it's definitely a gotcha. IIRC, this can cause issues for Hibernate as well.
Java has painted itself into a bit of a corner here. It provides a default behavior based on identity but then requires you override *two* functions if you need equality instead and forces you to lose identity (because you are forced to change hashCode() so that it no longer provides object identity). Essentially, if you apply a less discriminating equivalence relation to your objects, you are *forced* to make HashTable performance worse by changing hashCode() to be less unique as well.
The documentation for Collection.contains() specifies a dependency on equals() but that is clearly incorrect - and your assertion that it may use hashCode() seems correct. If contains() behaved per the documentation, it would obey object equality and ignore object identity.
That was the caveat I was trying to highlight (so perhaps we are just in agreement but coming at this from different positions?).