Java

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

AspectDetails
TopicList.of(), Set.of(), Map.of(), Collections.unmodifiableXxx(), copyOf()
ComplexityIntermediate
PrerequisitesPart 1 (Collection Framework Architecture)
Time to Master2-3 hours
Interview FrequencyHigh (defensive programming, API design)

๐ŸŽฏ What You'll Learn

After completing this article, you will be able to:

  1. Choose between unmodifiable wrappers and immutable factory methods
  2. Understand structural vs deep immutability
  3. Design APIs that return safe, immutable collections
  4. Handle null elements correctly in different collection types
  5. 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)
Code
Loading syntax highlighter...

The Problem

TEXT(15 lines)
Code
Loading syntax highlighter...

The Immutable Solution

JAVA(21 lines)
Code
Loading syntax highlighter...

The Difference

TEXT(11 lines)
Code
Loading syntax highlighter...

Mental Model: The Museum vs The Gift Shop

TEXT(42 lines)
Code
Loading 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)
Code
Loading syntax highlighter...

Internal Implementation

JAVA(49 lines)
Code
Loading syntax highlighter...

Available Wrappers

JAVA(9 lines)
Code
Loading syntax highlighter...

The Wrapper Problem

JAVA(12 lines)
Code
Loading syntax highlighter...

Deep Dive: Java 9+ Immutable Factory Methods

List.of(), Set.of(), Map.of()

JAVA(10 lines)
Code
Loading syntax highlighter...

Null Handling Differences

JAVA(16 lines)
Code
Loading syntax highlighter...

Internal Optimizations

JAVA(16 lines)
Code
Loading syntax highlighter...

Map.of() and Map.ofEntries()

JAVA(20 lines)
Code
Loading syntax highlighter...

Deep Dive: copyOf() Methods (Java 10+)

Creating Immutable Copies

JAVA(15 lines)
Code
Loading syntax highlighter...

Optimization: No Copy When Already Immutable

JAVA(16 lines)
Code
Loading syntax highlighter...

Null Rejection

JAVA(6 lines)
Code
Loading syntax highlighter...

Deep Dive: Structural vs Deep Immutability

The Critical Distinction

JAVA(22 lines)
Code
Loading syntax highlighter...

Achieving True Immutability

JAVA(21 lines)
Code
Loading syntax highlighter...

Immutability Hierarchy

TEXT(36 lines)
Code
Loading syntax highlighter...

Deep Dive: API Design with Immutable Collections

Returning Collections from Methods

JAVA(29 lines)
Code
Loading syntax highlighter...

Accepting Collections as Parameters

JAVA(17 lines)
Code
Loading syntax highlighter...

Builder Pattern with Immutable Result

JAVA(41 lines)
Code
Loading syntax highlighter...

Deep Dive: Records and Immutable Collections

Records with Collections

JAVA(25 lines)
Code
Loading syntax highlighter...

Complete Immutable Record Pattern

JAVA(35 lines)
Code
Loading syntax highlighter...

โš ๏ธ Common Mistakes

Mistake 1: Assuming Wrapper Provides Full Immutability

JAVA(13 lines)
Code
Loading syntax highlighter...

Mistake 2: Null Elements with Factory Methods

JAVA(12 lines)
Code
Loading syntax highlighter...

Mistake 3: Forgetting Element Mutability

JAVA(14 lines)
Code
Loading syntax highlighter...

Mistake 4: Expensive Repeated Copies

JAVA(21 lines)
Code
Loading syntax highlighter...

Mistake 5: Using List.of() for Large Collections

JAVA(18 lines)
Code
Loading syntax highlighter...

๐Ÿ› Debug This

Challenge 1: The Mystery Mutation

JAVA(7 lines)
Code
Loading syntax highlighter...
โœ… Answer:
UnsupportedOperationException! The Set from Set.of() is immutable. You cannot add elements to it.
Fix:
JAVA(5 lines)
Code
Loading syntax highlighter...

Challenge 2: The Inconsistent Equality

JAVA(7 lines)
Code
Loading syntax highlighter...
โœ… Answer:
JAVA(3 lines)
Code
Loading 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)
Code
Loading syntax highlighter...
โœ… Answer:
NullPointerException! List.copyOf() does not allow null elements.
Fix:
JAVA(7 lines)
Code
Loading syntax highlighter...

๐Ÿ’ป Exercises

Exercise 1: Immutable Configuration

Create a configuration holder that is fully immutable:

JAVA(5 lines)
Code
Loading syntax highlighter...
โœ… Solution:
JAVA(32 lines)
Code
Loading syntax highlighter...

Exercise 2: Safe Collection Return

Refactor this class to be safe:

JAVA(7 lines)
Code
Loading syntax highlighter...
โœ… Solution:
JAVA(32 lines)
Code
Loading syntax highlighter...

Exercise 3: Collection Factory Methods

Implement a utility class that creates immutable collections from various sources:

JAVA(5 lines)
Code
Loading syntax highlighter...
โœ… Solution:
JAVA(50 lines)
Code
Loading syntax highlighter...

๐ŸŽค Senior-Level Interview Questions

Question 1: Wrapper vs Factory

Q: What's the difference between Collections.unmodifiableList() and List.of()?
A:
AspectunmodifiableList()List.of()
TypeWrapper/viewTrue immutable
Backing collectionYes, can be modifiedNo backing collection
NullsAllowed (if source allows)Not allowed
MemoryExtra wrapper objectOptimized for size
Use caseTemporary immutabilityPermanent immutability
JAVA(8 lines)
Code
Loading syntax highlighter...

Question 2: Null Handling

Q: Why don't Java 9+ immutable collections allow null elements?
A:

Design decision for clarity and safety:

  1. Ambiguity: null can mean "absent" or "present but null"
  2. Bug prevention: NPE at creation time, not later in code
  3. Optimization: No null checks needed in internal iteration
  4. API clarity: Forces explicit handling of absence (Optional, empty collection)
JAVA(9 lines)
Code
Loading syntax highlighter...

Question 3: copyOf() Optimization

Q: When does List.copyOf() return the same instance instead of creating a copy?
A:

When the input is already an immutable list from the same family:

JAVA(11 lines)
Code
Loading syntax highlighter...

Question 4: Structural vs Deep Immutability

Q: Is List.of(mutableObject) truly immutable? Explain.
A:
It provides structural immutability, not deep immutability:
JAVA(17 lines)
Code
Loading syntax highlighter...

Question 5: Performance Considerations

Q: In a hot path called millions of times, which is better: caching an unmodifiable view or returning List.copyOf() each time?
A:

Cache the immutable copy, not create on each call:

JAVA(22 lines)
Code
Loading syntax highlighter...

๐Ÿ“ Summary & Key Takeaways

Immutability Approaches

MethodMutabilityNullsUse Case
Collections.unmodifiableXxx()View onlyYesTemporary protection
List.of() / Set.of() / Map.of()True immutableNoSmall known collections
List.copyOf()True immutableNoDefensive copies
Collectors.toUnmodifiableList()True immutableNoStream results

Best Practices

  1. Return immutable collections from APIs - prevents caller corruption
  2. Make defensive copies on input - protect internal state
  3. Use records with copyOf() - truly immutable data classes
  4. Cache immutable versions - avoid repeated copy creation
  5. 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:

  1. Wrappers provide view immutability - the underlying collection can still change
  2. Factory methods provide structural immutability - but elements may still be mutable
  3. True immutability requires immutable elements - use records, strings, or primitives
  4. Defensive copying protects both input and output - use copyOf() liberally
  5. 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