How do you create immutable/unmodifiable collections in Java?
Quick Answer
List.of()/Set.of()/Map.of() (Java 9+) create truly immutable collections that throw UnsupportedOperationException on any mutation attempt, and also reject null elements. Collections.unmodifiableList(list) (and similar) instead wrap an existing mutable collection in a read-only view — the view itself can't be modified, but changes to the underlying collection still show through it.
Detailed Answer
There are two distinct approaches, and they behave differently:
1. List.of(...), Set.of(...), Map.of(...) (Java 9+): create genuinely immutable collections. Any mutation attempt (add, remove, set) throws UnsupportedOperationException, and they reject null elements/keys/values outright (throwing NullPointerException at creation time).
List<String> l = List.of("a", "b", "c");
l.add("d"); // UnsupportedOperationException
List.of("a", null); // NullPointerException immediately
2. Collections.unmodifiableList(list) (and unmodifiableSet, unmodifiableMap, ...): wraps an existing mutable collection in a read-only view. The view itself rejects direct mutation, but it is not truly immutable — if code still holds a reference to the original mutable list and modifies it, those changes are visible through the wrapper, since it's just a thin delegating façade, not a copy.
List<String> mutable = new ArrayList<>(List.of("a", "b"));
List<String> view = Collections.unmodifiableList(mutable);
mutable.add("c");
view.get(2); // "c" — the wrapper sees the underlying change
Rule of thumb: prefer List.of()/Set.of()/Map.of() for genuinely fixed data (constants, defensive copies to hand out to callers); use Collections.unmodifiableX mainly when you need to expose a read-only view over a collection you still intend to mutate internally.