Immutable collections in Java
List.of, Set.of, Map.of, copyOf; no nulls; unmodifiable view vs immutable copy.
Set.of("a", "b", "a") doesn't quietly drop the duplicate. It throws. Java's immutable collections are strict on purpose. Here's why that's a good thing.Sealed at birth
**List.of, Set.of and Map.of (Java 9+) create collections that can never change**. add, remove, set and clear all throw **UnsupportedOperationException**.
List<String> days = List.of("Mon", "Tue");
Map<String, Integer> m = Map.of("a", 1, "b", 2);
days.add("Wed"); // throwsStrict about input
These factories treat suspicious input as a bug: **null elements, keys or values → NullPointerException. Duplicate Set.of elements or Map.of keys → IllegalArgumentException**. Compare new HashSet<>(...), which silently drops duplicates.
List.of("a", null); // NPE
Set.of("a", "a"); // IAE
Map.of("k", 1, "k", 2); // IAE
// NPE = NullPointerException
// IAE = IllegalArgumentExceptionYour turn
What happens?
Set<String> s = Set.of("x", "y", "x");
System.out.println(s.size());Prints 2Prints 3Throws IllegalArgumentException
Show the answer
Set.of sees the duplicate "x" at creation time and throws **IllegalArgumentException**. A duplicate in a literal set is almost always a typo.
Snapshot vs window
**List.copyOf(src) (Java 10+) takes an independent immutable snapshot. Collections.unmodifiableList(src) is only a read-only view**: later changes to src show through it.
var copy = List.copyOf(src); // frozen
var view = Collections.unmodifiableList(src);
// live window onto srcSnapshot or window?
What does this print?
var src = new ArrayList<>(List.of("a"));
var copy = List.copyOf(src);
var view = Collections.unmodifiableList(src);
src.add("b");
int c = copy.size(), v = view.size();
System.out.println(c + " " + v);2 21 21 1
Show the answer
copy froze one element. view still looks at src, so it sees the new element: 1 2.
Shallowly immutable
Immutable collections freeze which objects they hold, not what's inside those objects. A List.of holding StringBuilders can't swap elements, but each StringBuilder can still be changed.
var list = List.of(new StringBuilder("hi"));
list.get(0).append("!"); // allowed
System.out.println(list); // [hi!]Why teams love them
Immutable collections can be shared between threads with no locks and returned from methods with no defensive copying. A common pattern: records copy incoming lists with List.copyOf in their constructor, so no caller can change them later.
Key takeaways
- Any modification throws UnsupportedOperationException
- null elements, keys or values → NullPointerException
- Duplicate Set.of elements or Map.of keys → IllegalArgumentException
- copyOf = immutable snapshot; unmodifiableX = live read-only view
💡 List.of is a laminated menu; an unmodifiable view is a window onto the kitchen whiteboard — you can't write on it, but the chef can.
List.of has fixed-argument overloads for 0 to 10 elements plus a varargs version. The overloads avoid allocating a varargs array for the common small cases.
Practice questions
What happens when this runs?
List<String> list = List.of("a", null);
System.out.println(list.size());- 2
- 1
- Throws NullPointerException
- Compile error
Check your answer
Throws NullPointerException. The List.of family rejects null elements at creation time with NullPointerException.
What happens when this runs?
Set<String> s = Set.of("a", "b", "a");
System.out.println(s.size());- 2
- 3
- Throws IllegalArgumentException
- Throws UnsupportedOperationException
Check your answer
Throws IllegalArgumentException. Unlike new HashSet<>(...), which silently drops duplicates, Set.of treats a duplicate element as a programming mistake and throws IllegalArgumentException.