Immutability and Thread Safety in Java Collections

Immutable collection state can be shared safely between threads because readers cannot concurrently mutate the same collection. In Java, true immutability also depends on whether the elements themselves are mutable.

An immutable collection has a fixed set of elements after construction. Code that receives such a collection can read it concurrently without coordinating access to protect the collection's structure from writes.

Java provides several ways to expose collections without allowing callers to mutate them. `List.copyOf` creates an unmodifiable copy, while `Collections.unmodifiableList` creates an unmodifiable view over an existing list. The distinction matters because changes to the underlying list remain visible through the view.

Immutability removes one important source of data races: concurrent structural modification of the collection. It does not automatically make every object reachable from the collection thread-safe. For example, an immutable `List<Foo>` can still contain `Foo` objects whose fields are mutable.

Safe publication is a separate concern. A reference to an immutable object must itself become visible to another thread through an appropriate mechanism, such as a `final` field established during construction, a `volatile` reference, a lock, or a concurrent handoff mechanism. Once properly published, immutable state can generally be read concurrently without further synchronization.

Key points

Interview questions

Why can an immutable collection be safely shared between threads?

After construction, the collection's structure cannot be changed through its API. Multiple threads can therefore read the same structural state without racing with writes to that collection. This removes the need for synchronization solely to protect those reads from concurrent structural mutation. The elements themselves may still require their own thread-safety guarantees.

What is the difference between `List.copyOf` and `Collections.unmodifiableList`?

`List.copyOf` creates an unmodifiable collection based on the supplied collection's contents at creation time. `Collections.unmodifiableList` creates a read-only wrapper around an existing list. If the original backing list is subsequently changed, the unmodifiable view can observe those changes. Therefore, a read-only view should not automatically be described as an immutable snapshot.

Does an immutable collection make its elements immutable?

No. Collection immutability normally means the collection's structure cannot be changed, not that every referenced object is immutable. For example, an immutable list can contain a mutable object whose fields are changed by another thread. If those elements are shared concurrently, their own state must be designed or synchronized appropriately.

Why is safe publication still relevant for immutable objects?

Immutability controls what can happen after an object is correctly constructed and shared, but another thread still needs a valid visibility relationship to observe that object reliably. Java provides mechanisms such as `final` fields, `volatile`, synchronization, and concurrent queues for establishing the required happens-before relationships. Correctly constructed immutable objects are especially useful because once safely published, their state does not subsequently need synchronization for ordinary reads.

Would you use an immutable collection or a concurrent collection for shared application state?

The choice depends on whether the state needs to change after construction. If updates are infrequent or can be represented by replacing an entire immutable snapshot, immutable collections can simplify concurrent reads. If multiple threads need to mutate a shared collection directly, a concurrent collection may be more appropriate. The design should also consider update frequency, contention, atomicity requirements, and how readers observe updates.