Immutable collectionsmedium3-5 years

A method returns `Collections.unmodifiableList(orders)` so callers "can't mutate the list." Weeks later, a caller reports the returned list changed underneath them mid-iteration. What actually happened, and how is `List.of(...)` different?

Collections.unmodifiableList(orders) doesn't produce an immutable list — it produces a read-only window onto the exact same underlying orders list. The wrapper blocks the caller from calling add or remove through that window, but the original orders list is still sitting somewhere, still mutable, and if whoever owns it adds or removes an element, that change shows straight through the wrapper, because there's only one real list, and the wrapper is just a view over it that refuses one kind of access to it. If that mutation happens while a caller is iterating the wrapper, they get the same ConcurrentModificationException (or, worse, no exception at all and a skipped element) any live view produces under concurrent structural change. List.of(...) is genuinely different: it's not a view over anything, it copies its arguments into its own internal, fixed storage at construction, rejects null, and there is no mutable original anywhere for anyone to still hold a reference to and change.

The lesson behind it →