# Part 11 — Immutable Collections (Java 9+)
This is a common topic in modern Java interviews, especially for Java 11, 17, and 21.
Typical questions:
- What is the difference between immutable and unmodifiable collections?
- What are
List.of(),Set.of(), andMap.of()? - Why were immutable collection factories introduced in Java 9?
- What is defensive copying?
- Why are immutable collections thread-safe?
1. Why Immutable Collections?¶
Before Java 9:
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Spring");
If you wanted a read-only view:
List<String> readOnly =
Collections.unmodifiableList(list);
This works, but has an important limitation we'll discuss shortly.
Java 9 introduced dedicated immutable collection factories.
2. List.of()¶
Example:
List<String> languages =
List.of("Java", "Spring", "Kafka");
Properties:
- Immutable
- Cannot add elements
- Cannot remove elements
- Cannot replace elements
- Does not allow
null
Trying to modify it:
languages.add("Python");
Throws:
UnsupportedOperationException
3. Set.of()¶
Example:
Set<String> skills =
Set.of("Java", "Spring", "Docker");
Properties:
- Immutable
- No duplicate elements
- No
null
Duplicate example:
Set.of("Java", "Java");
Throws:
IllegalArgumentException
Unlike HashSet, duplicates are rejected during creation.
4. Map.of()¶
Example:
Map<Integer, String> map =
Map.of(
1, "Java",
2, "Spring",
3, "Kafka"
);
Properties:
- Immutable
- No duplicate keys
- No null keys
- No null values
5. Why null Is Not Allowed¶
Example:
List.of("Java", null);
Throws:
NullPointerException
Reason:
Immutable collections favor predictable behavior and avoid ambiguity around null values.
6. Immutable vs Unmodifiable¶
This is one of the most frequently asked interview questions.
Unmodifiable Collection¶
List<String> original =
new ArrayList<>();
original.add("Java");
List<String> readOnly =
Collections.unmodifiableList(original);
Trying:
readOnly.add("Spring");
Throws:
UnsupportedOperationException
But:
original.add("Spring");
Now:
System.out.println(readOnly);
Output:
[Java, Spring]
Why?
readOnly is only a view of the original list.
The underlying list is still mutable.
Immutable Collection¶
List<String> list =
List.of("Java", "Spring");
No underlying mutable list is exposed.
Contents never change.
7. Visual Comparison¶
Unmodifiable:
Original List
↓
Mutable
↓
Unmodifiable View
Changes to the original are visible through the view.
Immutable:
Immutable Object
↓
No modification possible
No external mutation can affect it.
8. Defensive Copying¶
Suppose:
class Student {
private List<String> subjects;
Student(List<String> subjects) {
this.subjects = subjects;
}
}
Problem:
subjects.add("Hacking");
The caller modifies internal state.
Better:
this.subjects =
List.copyOf(subjects);
Now the object's internal list cannot be modified through the original reference.
9. List.copyOf()¶
Example:
List<String> source =
new ArrayList<>();
source.add("Java");
List<String> copy =
List.copyOf(source);
copy is immutable.
Changing source later does not affect copy.
10. Set.copyOf()¶
Set<String> set =
Set.copyOf(existingSet);
Returns an immutable set.
11. Map.copyOf()¶
Map<Integer,String> copy =
Map.copyOf(existingMap);
Returns an immutable map.
12. Why Defensive Copying?¶
Suppose a REST service receives:
List<Order> orders
If you store the reference directly:
this.orders = orders;
The caller can modify your internal state after construction.
Safer:
this.orders = List.copyOf(orders);
This is a common pattern in immutable classes.
13. Thread Safety¶
Immutable collections are naturally thread-safe.
Example:
List<String> config =
List.of("A", "B", "C");
100 threads can read:
config.get(0);
No synchronization is required because the data never changes.
14. Performance¶
Immutable collections have several benefits:
- No resize operations
- No synchronization
- No structural modification bookkeeping
- Reduced implementation overhead
The JDK can also use specialized compact implementations for small immutable collections.
15. Memory Optimization¶
For example:
List.of("Java")
The JDK uses specialized internal classes for small immutable collections instead of a general-purpose ArrayList.
This reduces memory usage.
16. Collections.unmodifiableList()¶
Important distinction:
List<String> list =
new ArrayList<>();
List<String> view =
Collections.unmodifiableList(list);
View:
Read-only interface
Underlying list:
Still mutable
17. Immutable Class Example¶
public final class Employee {
private final List<String> skills;
public Employee(List<String> skills) {
this.skills = List.copyOf(skills);
}
public List<String> getSkills() {
return skills;
}
}
Advantages:
- Internal state cannot be modified externally.
- No defensive copy is needed in the getter because the stored list is already immutable.
18. Factory Methods Summary¶
| Method | Immutable | Allows Null | Allows Duplicates |
|---|---|---|---|
| List.of() | Yes | No | Yes |
| Set.of() | Yes | No | No |
| Map.of() | Yes | No | No duplicate keys |
19. Map.ofEntries()¶
Useful for many entries.
Map<Integer, String> map =
Map.ofEntries(
Map.entry(1, "Java"),
Map.entry(2, "Spring"),
Map.entry(3, "Kafka")
);
More readable than a very long Map.of(...).
20. Time Complexity¶
Immutable collections generally preserve the same lookup characteristics as their mutable counterparts for common operations like get() or contains(), but all structural modification methods throw UnsupportedOperationException.
Production Examples¶
Application Configuration¶
List<String> supportedCurrencies =
List.of("USD", "EUR", "INR");
Configuration should not change at runtime.
HTTP Headers¶
Map<String, String> headers =
Map.of(
"Content-Type", "application/json",
"Accept", "application/json"
);
Domain Model¶
public Order(List<Item> items) {
this.items = List.copyOf(items);
}
Protects internal state from callers.
Common Interview Questions¶
Q1. Difference between immutable and unmodifiable?¶
| Immutable | Unmodifiable |
|---|---|
| Cannot change at all | Read-only view |
| Safe from external modification | Underlying collection may still change |
Q2. Why introduce List.of()?¶
To provide concise, memory-efficient immutable collections without requiring wrapper methods.
Q3. Can immutable collections contain null?¶
No.
List.of(), Set.of(), and Map.of() reject null.
Q4. Difference between List.of() and Arrays.asList()?¶
| List.of() | Arrays.asList() |
|---|---|
| Immutable | Fixed-size |
| No null | Allows null |
add, remove, set all fail | add/remove fail, set succeeds |
Example:
List<String> list = Arrays.asList("A", "B");
list.set(0, "X"); // Works
list.add("C"); // Fails
Q5. Difference between List.copyOf() and Collections.unmodifiableList()?¶
List.copyOf() creates an immutable collection.
Collections.unmodifiableList() creates a read-only view over another collection.
Q6. Are immutable collections thread-safe?¶
Yes.
Because they cannot be modified after creation.
Q7. Why use defensive copying?¶
To prevent callers from modifying an object's internal state.
Q8. What happens with duplicate keys in Map.of()?¶
Throws:
IllegalArgumentException
Q9. Which is better for immutable domain objects?¶
List.copyOf()
rather than storing a mutable list reference.
Q10. Does Collections.unmodifiableList() make the original list immutable?¶
No.
It only prevents modification through the wrapper reference.
Senior Interview Scenario¶
Question:
Why is this class not truly immutable?
class Employee {
private final List<String> skills;
Employee(List<String> skills) {
this.skills = skills;
}
public List<String> getSkills() {
return skills;
}
}
Answer:
The constructor stores the caller's mutable list directly. If the caller later modifies that list, the Employee object changes as well.
Correct approach:
this.skills = List.copyOf(skills);
Now the internal state cannot be changed externally.
Key Takeaways¶
List.of(),Set.of(), andMap.of()create immutable collections.- Immutable collections reject
null;Set.of()andMap.of()also reject duplicates. Collections.unmodifiableList()creates a read-only view, not an immutable collection.List.copyOf()is the preferred way to perform defensive copying.- Immutable collections are naturally thread-safe for concurrent reads.
- Defensive copying is a key practice when designing immutable classes and APIs.
Next: Part 12 — Collections Utility Class Deep Dive¶
We'll cover:
Collections.sort()binarySearch()reverse()shuffle()rotate()swap()frequency()min()/max()synchronizedCollection()unmodifiableCollection()- Production use cases and interview questions