Java 9 Immutable Collection Factories — List.of, Set.of, Map.of
Fast recall
Quick revision
Core answer
List, Set, and Map for creating truly immutable collections: List.of(a, b, c), Set.of(a, b, c), Map.of(k1, v1, k2, v2), plus Map.ofEntries(Map.entry(k, v), ...) for maps with more than 10 entries. These differ from Collections.unmodifiableList(new ArrayList<>(...)) in three important ways: (1) Real immutability — the returned collection throws UnsupportedOperationException on any mutation, and there's no backing collection someone can modify behind your back (unlike unmodifiableList, which wraps a mutable list that the caller might still hold). (2) Null-intolerant — List.of(null) throws NPE; nulls are forbidden as elements, keys, or values. (3) Structurally optimized — the JVM returns compact, purpose-built classes (ImmutableCollections.ListN, Set12, Map1, etc.), smaller and faster than a wrapped ArrayList. `Map.copyOf(map)`, `List.copyOf(collection)`, `Set.copyOf(collection)` (also Java 10+) make defensive copies in one call. Use these for constants, DTO field initializers, test fixtures, and anywhere an immutable collection is the right signal. Caveat: they're structurally immutable — if elements themselves are mutable (e.g., List), the contents can still change. For deeply immutable data, combine with records and immutable element types.Whether the candidate knows the Java 9 factories exist, their immutability guarantees, null intolerance, and how they improve on `Collections.unmodifiableList`
Thinking `List.of` is just syntactic sugar for `Arrays.asList`. Confusing `Arrays.asList` (fixed-size but mutable via set) with `List.of` (truly immutable). Forgetting that nulls aren't allowed
Mention structural vs deep immutability, discuss the specialized internal classes (ListN, Set12), explain `List.copyOf` as defensive-copy shortcut, and note Map.of's 10-pair limit with Map.ofEntries as the alternative
Interview-ready guide
Interview answer
Java 9 added concise static factory methods on List, Set, and Map for creating truly immutable collections:
List.of(a, b, c)— immutable listSet.of(a, b, c)— immutable set (duplicates throwIllegalArgumentException)Map.of(k1, v1, k2, v2)— immutable map, up to 10 key/value pairsMap.ofEntries(Map.entry(k, v), ...)— arbitrary number of entries- Java 10+:
List.copyOf,Set.copyOf,Map.copyOf— defensive copies
Three key differences vs `Collections.unmodifiableList`
1. True immutability
Collections.unmodifiableListis a view over a mutable list. If the original leaks, immutability leaks.List.ofhas no backing mutable collection — nothing to mutate behind your back.
2. Null-hostile
List.of(null)throws NPE- Nulls are forbidden as elements, keys, or values
- Prevents a whole class of subtle bugs
3. Structurally optimized
- JVM returns specialized internal classes:
List1,List2,ListN,Set12,Map1,MapN - Smaller and often faster than
ArrayList+unmodifiableListwrapper - No resize buffer, no modCount, no backing array for small sizes
Other properties
- Throw
UnsupportedOperationExceptionon any mutation - Duplicates in
Set.ofandMap.ofthrowIllegalArgumentException - Serializable by design
- No guaranteed iteration order for Set/Map — don't build assertions on it
`Map.of` has a 10-pair limit
- The alternating-arg overloads stop at 10
- Beyond that:
Map.ofEntries(Map.entry(k1, v1), Map.entry(k2, v2), ...) - Slightly more verbose but scales
Common patterns
- Constants:
static final ListLOCALES = List.of(...) - DTO/record initializers:
new ApiResponse<>(data, Map.of())for empty meta - Test fixtures
- Defensive copies:
List.copyOf(untrustedList) - Stream to immutable:
stream.toList()(Java 16+) orstream.collect(Collectors.toUnmodifiableList())
vs `Arrays.asList`
Arrays.asListis fixed-size but mutable viaset— legacy trapList.ofis truly immutableArrays.asListallows nulls;List.ofdoesn't- Prefer
List.ofunless you specifically need the legacy behavior
Structural vs deep immutability
List.of(mutableMap)— the list is immutable but the map inside is still mutable- For deep immutability, make elements themselves immutable (records, primitives, String)
`copyOf` optimization
List.copyOf(source)checks if source is already the expected immutable type and returns it as-is- Zero allocation for repeated defensive copies
Recommendation: Reach for `List.of`, `Set.of`, `Map.of` as your default for constants, DTO initializers, and test fixtures. They're more concise, truly immutable, and null-safe. Use `List.copyOf` etc. for defensive copies when accepting collections from callers — the optimization skips the allocation if the input is already immutable. Only fall back to `Collections.unmodifiableList(new ArrayList<>(...))` when you need to allow nulls (rare and usually a design smell). Remember the 10-pair limit on Map.of; for bigger maps use `Map.ofEntries`. And always keep structural vs deep immutability in mind — List.of(mutableMap) is only shallow-immutable; for deep immutability, make the elements immutable too.
Java 9 Immutable Collection Factories — `List.of`, `Set.of`, `Map.of`
Direct Answer
Java 9 added concise static factory methods on List, Set, and Map for creating truly immutable collections: List.of(a, b, c), Set.of(a, b, c), Map.of(k1, v1, k2, v2), plus Map.ofEntries(Map.entry(k, v), ...) for maps with more than 10 entries. These differ from Collections.unmodifiableList(new ArrayList<>(...)) in three important ways: (1) Real immutability — the returned collection throws UnsupportedOperationException on any mutation, and there's no backing collection someone can modify behind your back (unlike unmodifiableList, which wraps a mutable list that the caller might still hold). (2) Null-intolerant — List.of(null) throws NPE; nulls are forbidden as elements, keys, or values. (3) Structurally optimized — the JVM returns compact, purpose-built classes (ImmutableCollections.ListN, Set12, Map1, etc.), smaller and faster than a wrapped ArrayList. Map.copyOf(map), List.copyOf(collection), Set.copyOf(collection) (also Java 10+) make defensive copies in one call. Use these for constants, DTO field initializers, test fixtures, and anywhere an immutable collection is the right signal. Caveat: they're structurally immutable — if elements themselves are mutable (e.g., List<Map<String, Object>>), the contents can still change. For deeply immutable data, combine with records and immutable element types.
Common Mistakes & Pitfalls
Thinking List.of is just syntactic sugar for Arrays.asList. Confusing Arrays.asList (fixed-size but mutable via set) with List.of (truly immutable). Forgetting that nulls aren't allowed
Check your understanding
Answer these without looking back at the article.
- What's the difference between `List.of` and `Arrays.asList`?
- Why does `List.of` throw on null elements?
- When would you use `Map.ofEntries` instead of `Map.of`?
- How is `List.copyOf` optimized when the source is already immutable?
- What's the difference between `Stream.toList()` (Java 16+) and `List.of`?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.