DEV Community

Thellu
Thellu

Posted on

1

🚨How Incorrect `equals()` and `hashCode()` Implementations Cause Memory Leaks in Java

When we talk about memory leaks in Java, many developers immediately think of listeners not being removed or caches growing indefinitely. But there's a more subtle and surprisingly common culprit: incorrect implementations of equals() and hashCode().

This post walks through how these two methods — when implemented poorly — can quietly wreak havoc on your application's memory.


The Basics: equals() and hashCode()

In Java, equals() and hashCode() are used to determine object equality and how objects are stored in hash-based collections like HashMap or HashSet.

A correct contract is:

  • If two objects are equal (a.equals(b) == true), they must return the same hashCode().
  • If two objects are not equal, it is not mandatory to return different hash codes — but doing so improves performance.

🔥 What Happens If You Break the Contract?

Let’s look at a real-world-style scenario:

import java.util.HashSet;
import java.util.Set;

public class User {
    private String username;

    public User(String username) {
        this.username = username;
    }

    // Oops! No equals() or hashCode() override!

    public static void main(String[] args) {
        Set<User> users = new HashSet<>();

        for (int i = 0; i < 1000000; i++) {
            users.add(new User("john_doe"));
        }

        System.out.println(users.size()); // Expecting 1? Nope.
    }
}
Enter fullscreen mode Exit fullscreen mode

Expected size? 1

Actual size? 1,000,000

Why? Because each User("john_doe") is a new object with the default hashCode() and equals() — meaning Java thinks they are all different.


💣 Where Memory Leaks Come In

Collections like HashMap and HashSet rely on these two methods for deduplication and key management. If you don’t override them properly, you can:

  • Create duplicate keys in maps
  • Grow collections indefinitely
  • Make it impossible to remove objects (since .remove(obj) depends on equals() working)

And if these collections live for the lifetime of your application — congratulations, you now have a memory leak.


A Real Example: Caching Gone Wrong

Don't think you'll never write stupid code like this, see this example:

Map<User, CachedResult> cache = new HashMap<>();

public CachedResult getResult(User user) {
    return cache.computeIfAbsent(user, this::expensiveOperation);
}
Enter fullscreen mode Exit fullscreen mode

If User doesn't correctly implement equals() and hashCode(), this cache will grow indefinitely, because Java can never recognize that the same user was already cached.


How to Fix It

Always override both equals() and hashCode() together.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    User user = (User) o;
    return Objects.equals(username, user.username);
}

@Override
public int hashCode() {
    return Objects.hash(username);
}
Enter fullscreen mode Exit fullscreen mode

Or better yet, use Lombok:

@EqualsAndHashCode
public class User {
    private String username;
}
Enter fullscreen mode Exit fullscreen mode

Pro Tip: Watch Out in Unit Tests

Unit tests might not catch this kind of issue if you're only checking content — not behavior in collections. Be sure to test scenarios involving Map, Set, or deduplication logic.


Conclusion

It’s easy to forget the equals()/hashCode() contract until it’s too late. But when misused, they can cause memory leaks that are hard to track down.

So next time you're working with custom objects used as keys in maps or elements in sets — stop and ask: Do I really implement equals() and hashCode() correctly?

Gen AI apps are built with MongoDB Atlas

Gen AI apps are built with MongoDB Atlas

MongoDB Atlas is the developer-friendly database for building, scaling, and running gen AI & LLM apps—no separate vector DB needed. Enjoy native vector search, 115+ regions, and flexible document modeling. Build AI faster, all in one place.

Start Free

Top comments (0)

Build gen AI apps that run anywhere with MongoDB Atlas

Build gen AI apps that run anywhere with MongoDB Atlas

MongoDB Atlas bundles vector search and a flexible document model so developers can build, scale, and run gen AI apps without juggling multiple databases. From LLM to semantic search, Atlas streamlines AI architecture. Start free today.

Start Free

👋 Kindness is contagious

Explore this practical breakdown on DEV’s open platform, where developers from every background come together to push boundaries. No matter your experience, your viewpoint enriches the conversation.

Dropping a simple “thank you” or question in the comments goes a long way in supporting authors—your feedback helps ideas evolve.

At DEV, shared discovery drives progress and builds lasting bonds. If this post resonated, a quick nod of appreciation can make all the difference.

Okay