Immutable Collections
Master immutability in Java collections. Learn the evolution from Collections.unmodifiableXxx() wrappers to Java 9+ factory methods, understand structural vs deep immutability, and build bulletproof APIs.
๐ At a Glance
| Aspect | Details |
|---|---|
| Topic | List.of(), Set.of(), Map.of(), Collections.unmodifiableXxx(), copyOf() |
| Complexity | Intermediate |
| Prerequisites | Part 1 (Collection Framework Architecture) |
| Time to Master | 2-3 hours |
| Interview Frequency | High (defensive programming, API design) |
๐ฏ What You'll Learn
After completing this article, you will be able to:
- Choose between unmodifiable wrappers and immutable factory methods
- Understand structural vs deep immutability
- Design APIs that return safe, immutable collections
- Handle null elements correctly in different collection types
- Use copyOf() for defensive copies efficiently
Production Story: The Mutant Configuration
The Incident
Our configuration service was returning cached settings to multiple threads. One team "optimized" their code by modifying the returned list directly:
JAVA(19 lines)CodeLoading syntax highlighter...
The Problem
TEXT(15 lines)CodeLoading syntax highlighter...
The Immutable Solution
JAVA(21 lines)CodeLoading syntax highlighter...
The Difference
TEXT(11 lines)CodeLoading syntax highlighter...
Mental Model: The Museum vs The Gift Shop
TEXT(42 lines)CodeLoading syntax highlighter...
Interactive Visualization
See what happens when you try to modify immutable collections:
Loading visualizer...
Deep Dive: Collections.unmodifiableXxx() Wrappers
How Wrappers Work
JAVA(14 lines)CodeLoading syntax highlighter...
Internal Implementation
JAVA(49 lines)CodeLoading syntax highlighter...
Available Wrappers
JAVA(9 lines)CodeLoading syntax highlighter...
The Wrapper Problem
JAVA(12 lines)CodeLoading syntax highlighter...
Deep Dive: Java 9+ Immutable Factory Methods
List.of(), Set.of(), Map.of()
JAVA(10 lines)CodeLoading syntax highlighter...
Null Handling Differences
JAVA(16 lines)CodeLoading syntax highlighter...
Internal Optimizations
JAVA(16 lines)CodeLoading syntax highlighter...
Map.of() and Map.ofEntries()
JAVA(20 lines)CodeLoading syntax highlighter...
Deep Dive: copyOf() Methods (Java 10+)
Creating Immutable Copies
JAVA(15 lines)CodeLoading syntax highlighter...
Optimization: No Copy When Already Immutable
JAVA(16 lines)CodeLoading syntax highlighter...
Null Rejection
JAVA(6 lines)CodeLoading syntax highlighter...
Deep Dive: Structural vs Deep Immutability
The Critical Distinction
JAVA(22 lines)CodeLoading syntax highlighter...
Achieving True Immutability
JAVA(21 lines)CodeLoading syntax highlighter...
Immutability Hierarchy
TEXT(36 lines)CodeLoading syntax highlighter...
Deep Dive: API Design with Immutable Collections
Returning Collections from Methods
JAVA(29 lines)CodeLoading syntax highlighter...
Accepting Collections as Parameters
JAVA(17 lines)CodeLoading syntax highlighter...
Builder Pattern with Immutable Result
JAVA(41 lines)CodeLoading syntax highlighter...
Deep Dive: Records and Immutable Collections
Records with Collections
JAVA(25 lines)CodeLoading syntax highlighter...
Complete Immutable Record Pattern
JAVA(35 lines)CodeLoading syntax highlighter...
โ ๏ธ Common Mistakes
Mistake 1: Assuming Wrapper Provides Full Immutability
JAVA(13 lines)CodeLoading syntax highlighter...
Mistake 2: Null Elements with Factory Methods
JAVA(12 lines)CodeLoading syntax highlighter...
Mistake 3: Forgetting Element Mutability
JAVA(14 lines)CodeLoading syntax highlighter...
Mistake 4: Expensive Repeated Copies
JAVA(21 lines)CodeLoading syntax highlighter...
Mistake 5: Using List.of() for Large Collections
JAVA(18 lines)CodeLoading syntax highlighter...
๐ Debug This
Challenge 1: The Mystery Mutation
JAVA(7 lines)CodeLoading syntax highlighter...
UnsupportedOperationException! The Set from Set.of() is immutable. You cannot add elements to it.JAVA(5 lines)CodeLoading syntax highlighter...
Challenge 2: The Inconsistent Equality
JAVA(7 lines)CodeLoading syntax highlighter...
JAVA(3 lines)CodeLoading syntax highlighter...
List equality is based on contents (same elements in same order), not implementation type. This is by design - you can compare ArrayList to LinkedList to ImmutableList.
Challenge 3: The Missing Element
JAVA(2 lines)CodeLoading syntax highlighter...
NullPointerException! List.copyOf() does not allow null elements.JAVA(7 lines)CodeLoading syntax highlighter...
๐ป Exercises
Exercise 1: Immutable Configuration
Create a configuration holder that is fully immutable:
JAVA(5 lines)CodeLoading syntax highlighter...
JAVA(32 lines)CodeLoading syntax highlighter...
Exercise 2: Safe Collection Return
Refactor this class to be safe:
JAVA(7 lines)CodeLoading syntax highlighter...
JAVA(32 lines)CodeLoading syntax highlighter...
Exercise 3: Collection Factory Methods
Implement a utility class that creates immutable collections from various sources:
JAVA(5 lines)CodeLoading syntax highlighter...
JAVA(50 lines)CodeLoading syntax highlighter...
๐ค Senior-Level Interview Questions
Question 1: Wrapper vs Factory
Collections.unmodifiableList() and List.of()?| Aspect | unmodifiableList() | List.of() |
|---|---|---|
| Type | Wrapper/view | True immutable |
| Backing collection | Yes, can be modified | No backing collection |
| Nulls | Allowed (if source allows) | Not allowed |
| Memory | Extra wrapper object | Optimized for size |
| Use case | Temporary immutability | Permanent immutability |
JAVA(8 lines)CodeLoading syntax highlighter...
Question 2: Null Handling
Design decision for clarity and safety:
- Ambiguity:
nullcan mean "absent" or "present but null" - Bug prevention: NPE at creation time, not later in code
- Optimization: No null checks needed in internal iteration
- API clarity: Forces explicit handling of absence (Optional, empty collection)
JAVA(9 lines)CodeLoading syntax highlighter...
Question 3: copyOf() Optimization
List.copyOf() return the same instance instead of creating a copy?When the input is already an immutable list from the same family:
JAVA(11 lines)CodeLoading syntax highlighter...
Question 4: Structural vs Deep Immutability
List.of(mutableObject) truly immutable? Explain.JAVA(17 lines)CodeLoading syntax highlighter...
Question 5: Performance Considerations
Cache the immutable copy, not create on each call:
JAVA(22 lines)CodeLoading syntax highlighter...
๐ Summary & Key Takeaways
Immutability Approaches
| Method | Mutability | Nulls | Use Case |
|---|---|---|---|
Collections.unmodifiableXxx() | View only | Yes | Temporary protection |
List.of() / Set.of() / Map.of() | True immutable | No | Small known collections |
List.copyOf() | True immutable | No | Defensive copies |
Collectors.toUnmodifiableList() | True immutable | No | Stream results |
Best Practices
- Return immutable collections from APIs - prevents caller corruption
- Make defensive copies on input - protect internal state
- Use records with copyOf() - truly immutable data classes
- Cache immutable versions - avoid repeated copy creation
- Prefer List.of() for literals - compact, optimized, clear intent
Production Checklist
- API methods return immutable or defensive copies
- Constructors make defensive copies of collection parameters
- Records use copyOf() in compact constructors
- Null handling is explicit (reject or filter)
- Mutable elements are documented or avoided
- Hot paths cache immutable versions
- Tests verify immutability (modification attempts throw)
๐ Conclusion
Immutable collections are fundamental to safe, predictable Java applications. The evolution from Collections.unmodifiableXxx() wrappers to Java 9+ factory methods represents a significant improvement in both safety and expressiveness. Remember:
- Wrappers provide view immutability - the underlying collection can still change
- Factory methods provide structural immutability - but elements may still be mutable
- True immutability requires immutable elements - use records, strings, or primitives
- Defensive copying protects both input and output - use copyOf() liberally
- Null rejection is a feature - it catches bugs early
In the next article, we'll explore Java 21+ Sequenced Collections - a major evolution in the Collections Framework that finally gives us consistent "first" and "last" operations across collection types.
๐ Review Schedule
To solidify your understanding, review this material:
- Tomorrow: Practice converting mutable collections to immutable
- In 3 days: Implement a fully immutable domain class with collections
- In 1 week: Review wrapper vs factory method trade-offs
- In 2 weeks: Audit existing code for immutability opportunities