The design intent and the implementation of List.copyOf and the other copyOf methods is to do exactly as you would like, and it should be suitable for making defensive copies. The idea is that List.copyOf always makes a copy, unless it can prove that not making a copy is safe.
Additional context from the List.copyOf specification is:
Returns an unmodifiable List containing the elements of the given Collection, in its iteration order. The given Collection must not be null, and it must not contain any null elements. If the given Collection is subsequently modified, the returned List will not reflect such modifications.
Implementation Note:
If the given Collection is an unmodifiable List, calling copyOf will generally not create a copy.
The key assertion is the last sentence in the first paragraph, which says that if the argument is subsequently modified, that modification doesn't occur in the returned list. That is, the returned list is conceptually always a copy.
Note also that "unmodifiable List" is a link to a particular section of the List specification where it describes the List instances returned by List.of, List.copyOf, and stream's Collectors.toUnmodifiableList.
The instance returned by Collections.unmodifiableList is not an "unmodifiable List" in this definition. Rather, it's an unmodifiable view. Such views explicitly permit modification of the backing collection, if one has a reference to the backing collection, and if that collection is itself modifiable.
Yes, the distinction between "unmodifiable list" and "unmodifiable view" is confusing, but it's what we've settled on. Sorry. An alternative at one point was "immutable". But this is inaccurate, as those instances can in fact be mutable, so we've avoided use of "immutable" for these cases.
In any case, the actual behavior reflects this specification:
var list1 = new ArrayList<String>(Arrays.asList("a", "b", "c"))
var list2 = Collections.unmodifiableList(list1)
var list3 = List.copyOf(list2)
var list4 = List.copyOf(list3)
list2 == list3
==> false
list3 == list4
==> true
list1.add("d")
list2
==> [a, b, c, d]
list3
==> [a, b, c]
In other words, List.copyOf does make a defensive copy of the unmodifiable view, and this behavior is required by the specification.