Java's HashMap is the workhorse of every codebase. You put a key, you put a value, the value sticks around forever — or at least until you call remove. Simple, predictable.
But Java has another map sitting right next to it that almost nobody uses: WeakHashMap. Same Map<K, V> interface, very different behaviour. In a WeakHashMap, entries can disappear on their own. The garbage collector reaches in, removes the key, and the value goes with it. No remove call, no method invocation — they're just gone the next time you look.
Sometimes that's exactly what you want. Sometimes it's the source of a bug that's near-impossible to reproduce. Let's pull it apart.
A refresher on references
Java's garbage collector decides whether an object is collectable based on reachability: can the GC, starting from the live program, find a path of references to the object? If yes, the object stays. If not, it can be collected.
Not all references are equal:
- Strong reference — the normal kind.
Object o = new Object();creates one. As long as a strong reference exists, the object isn't going anywhere. - Soft reference — collectable, but only when the JVM is low on memory. Useful for memory-sensitive caches.
- Weak reference — collectable as soon as no strong references remain. The GC doesn't wait.
- Phantom reference — never returns the object; used for cleanup hooks.
HashMap holds strong references to both its keys and its values. That's why entries stay forever: the map itself is keeping them alive.
WeakHashMap, as the name suggests, holds weak references to its keys. Values are still strongly held, but the key — the thing that decides whether the entry exists at all — is weak.
What this means in practice
When you put an entry in a WeakHashMap:
1WeakHashMap<User, Session> sessions = new WeakHashMap<>();
2User alice = loadUser("alice");
3sessions.put(alice, new Session(...));
The map holds a weak reference to the User object. As long as something else (your code, a stack frame, another collection) holds a strong reference to alice, the entry stays.
The moment every strong reference to that User disappears:
1alice = null; // or it falls out of scope
…the GC is free to collect the User. And when it does, the corresponding entry in WeakHashMap vanishes. The next call to sessions.size() will return one less than before. The associated Session also becomes eligible for collection if nothing else holds it.
When this is exactly what you want
The original use case for WeakHashMap is metadata attached to objects you don't own. Imagine you're writing a library and you want to associate some bookkeeping data with User objects passed in by the caller. You can't add a field to User — it's the caller's class. You could keep a HashMap<User, Metadata>, but then your map keeps every User alive forever, which is a memory leak.
WeakHashMap<User, Metadata> is the answer. As long as the caller is using a particular User, the entry stays. The moment the caller is done with it, your metadata also disappears — no leak.
When this misbehaves
There's a subtle trap that catches a lot of people: a strong reference from the value back to the key.
1class Session {
2 final User user; // strong reference to the key
3 // ...
4}
Now the value (Session) holds the key (User) strongly. Even if your application code releases its reference to alice, the entry in the map is keeping alice alive — through its own value. The weak reference can never become "the only" reference, because the value holds a strong one.
The map never shrinks. You have all the complexity of WeakHashMap and none of the benefit.
Also: don't use it as a cache
It's tempting to use WeakHashMap for caching. "I want entries to expire when nobody's looking at them — perfect!" But it's not quite the cache you're imagining:
- A
WeakHashMapentry disappears when nothing else references the key. That timing is at the GC's discretion — it might be tomorrow, it might be the next minor allocation. - A real cache wants predictable retention: by time, by access, by size.
For caches, reach for:
SoftReference-based caches if you want memory-sensitive eviction.- Guava's
Cacheor Caffeine if you want LRU, time-based eviction, or any other configurable policy. WeakHashMaponly when "as long as the key is reachable" matches your needs exactly.
A quick demo
src/main/java/example/WeakDemo.java 1import java.util.WeakHashMap;
2
3public class WeakDemo {
4 public static void main(String[] args) throws InterruptedException {
5 WeakHashMap<Object, String> map = new WeakHashMap<>();
6
7 Object key = new Object();
8 map.put(key, "the value");
9
10 System.out.println("Before nulling: " + map.size()); // 1
11
12 key = null; // drop the only strong reference
13 System.gc(); // request a GC
14 Thread.sleep(100); // give it a beat
15
16 System.out.println("After nulling: " + map.size()); // 0 (usually)
17 }
18}
How other languages think about it
The same question — "should the value go when the key goes?" — comes up everywhere, and different languages answer it differently:
- Python has both
WeakValueDictionary(weak values, strong keys) andWeakKeyDictionary(weak keys, strong values). Pick the one that matches your ownership model. - Go has no built-in weak references. You're expected to remove entries explicitly. There's been discussion about adding them, but for now it's a manual job.
- C# offers
ConditionalWeakTable<TKey, TValue>for exactly the metadata-on-foreign-objects use case — it's the closest direct equivalent to Java'sWeakHashMap. - JavaScript has
WeakMap. Same idea: weak keys, strong values, no enumeration of keys.
Wrap-up
HashMap keeps everything you give it until you take it back. WeakHashMap returns a fraction of that control to the garbage collector: as long as somebody else cares about the key, the entry stays; the moment they stop, it can disappear.
That model is wonderful for "associate data with objects I don't own without leaking memory." It's a poor fit for caching or for any situation where the value transitively keeps the key alive. And it's a fascinating window into how the JVM thinks about object lifetimes — because the answer to "if a key dies, does the value die too?" turns out to be: almost always, and that's the point.
