Yes — HashSet allows exactly one null element. That's because HashSet is backed by HashMap, which permits one null key. Adding null a second time returns false and leaves the set unchanged, just like adding any other duplicate.
Why One Null Works
HashMap special-cases null keys: they always hash to bucket 0 (via the internal hash(null) == 0 rule), bypassing the usual key.hashCode() call. Equality for null keys is checked by reference (==). Since HashSet.add(e) delegates to map.put(e, PRESENT), the same null-handling applies.
Set<String> s = new HashSet<>();
s.add(null); // returns true — added
s.add(null); // returns false — already present
s.add("a");
s.contains(null); // true
s.size(); // 2
Contrast with the Other Sets
LinkedHashSet— same asHashSet: one null allowed (backed byLinkedHashMap).TreeSet— no null allowed. Adding null throwsNullPointerExceptionbecause the tree must callcompareToon the new element, andnull.compareTo(...)is impossible. (With a customComparatorthat tolerates null, you could in principle accept it, but the defaultnaturalOrderwon't.)Set.of(...)— null-hostile by design.Set.of(null)throws NPE at construction, andSet.of("a").contains(null)also throws NPE on lookup.ConcurrentHashMap-backed sets (e.g.,ConcurrentHashMap.newKeySet()) — no null allowed, becausenullis used as a sentinel for "key absent" internally.
new TreeSet<String>().add(null); // NullPointerException
Set.of("a", null); // NullPointerException
Set.of("a").contains(null); // NullPointerException
ConcurrentHashMap.<String>newKeySet().add(null); // NullPointerException
Why You Probably Shouldn't
Even where it's allowed, storing null in a Set is usually a code smell:
- Stream pipelines break.
stream().map(String::length)will NPE on a null element. for-eachis fragile. You constantly need null-guards inside the loop.Set.of/Set.copyOfinterop fails. You can't snapshot or transform via the modern immutable factories.- It hides intent. A null in a set of users probably means "missing" — model that explicitly with
Optionalor a sentinel.
The framework's null-hostility (visible in Set.of, ConcurrentHashMap, TreeSet) reflects the maintainers' position that allowing null was a Java 1.2 mistake they couldn't fix without breaking compatibility.