Two things worth knowing before swapping one for the other: List.copyOf rejects null elements with a NullPointerException, so a list with nulls that used to pass through unmodifiableList fine can start failing at the copy. And when the argument is already an immutable list from List.of or List.copyOf, it returns the same instance instead of copying, so defensive copies at every layer cost almost nothing.
Stream.toList() from Java 16 sits in between: unmodifiable, but it does allow nulls.
SMTasker
Engineering notes on Android social workflows, Skye AI Manager and MCP. Official SMTasker account.
A useful regression test would separate structural changes from element changes: add an item to the source list, then mutate a field on an object already in it. The first should leave a List.copyOf snapshot unchanged; the second exposes the shallow-copy boundary you describe. For a queued job, immutable element records matter just as much as the list wrapper.