In Java, UnsupportedOperationException is an unchecked exception that a method throws when the object does not support the requested operation. With lists, the exception means that the code calls a method such as add() or set() on a list that does not allow that change. The usual fix is to copy the values into a new ArrayList and change the copy.
We see the exception most often with lists from Arrays.asList() or List.of(), because both methods return lists that are fixed size or unmodifiable. Sets and unmodifiable maps from Set.of() or Map.of() throw the same exception on a change.
The following example shows the failing call on a fixed-size playlist and the fix with a modifiable copy.
List<String> genres = Arrays.asList("jazz", "rock"); // fixed size
boolean added = genres.add("pop"); // UnsupportedOperationException
List<String> playlist = new ArrayList<>(genres); // modifiable copy
boolean fixed = playlist.add("pop"); // true, playlist = [jazz, rock, pop]
Notice that the copy has the same elements, but it is an ArrayList, which supports every change.
We start with the reason why lists throw the exception, and with the list types and methods that throw it. After that, we fix the exception in four ways and read the stack trace to find the list that caused it. The last section shows when we throw the exception in our own code.
1. Root Cause of UnsupportedOperationException
The UnsupportedOperationException class is a member of the Java Collections Framework since Java 1.2. It extends RuntimeException, so it is an unchecked exception and does not need to be declared in the throws clause of a method or a constructor.
public class UnsupportedOperationException extends RuntimeException
The root cause is the design of the collection interfaces. The List interface marks methods such as add(), remove(), set() and clear() as optional operations. A list class that does not support an optional operation still has the method, because the interface requires it, but the method throws an UnsupportedOperationException.
So the compiler accepts list.add(“pop”) on every List, and the error shows up only at runtime, when the list object behind the variable does not allow the change. The same rule applies to other collection types, such as Set and Map.
1.1. Fixed-Size Lists From Arrays.asList()
One of the most common occurrences is while using the Arrays.asList() method. The method asList() returns a fixed-size list, so the add() and remove() methods are not supported. The list is backed by the array, and an array cannot change its length. The set() method works, because it does not change the size.
List<String> genres = Arrays.asList("jazz", "rock", "pop");
String old = genres.set(0, "blues"); // jazz, genres = [blues, rock, pop]
boolean added = genres.add("soul"); // UnsupportedOperationException
boolean removed = genres.remove("rock"); // UnsupportedOperationException: remove
We get the UnsupportedOperationException in the console for the add() call. The first line of the stack trace names AbstractList.add(), the inherited method that the list class behind Arrays.asList() does not override.
java.lang.UnsupportedOperationException
at java.base/java.util.AbstractList.add(AbstractList.java:155)
at java.base/java.util.AbstractList.add(AbstractList.java:113)
at com.howtodoinjava.core.collections.list.unsupported.UnsupportedOperationExceptionExample.stackTraces(UnsupportedOperationExceptionExample.java:37)
at com.howtodoinjava.core.collections.list.unsupported.UnsupportedOperationExceptionExample.main(UnsupportedOperationExceptionExample.java:20)
1.2. Unmodifiable Lists From List.of(), Stream.toList() and Collections
An unmodifiable list throws the exception on every change, including set(). The lists from List.of() (Java 9), Stream.toList() (Java 16) and Collections.unmodifiableList() belong to this group, along with the lists from Collections.emptyList() and Collections.singletonList().
For example, a music app loads the default genres with List.of(), and a later feature lets users add their own genre to the same list. Tests that build their own ArrayList pass, but the feature fails in production on the first add().
List<String> defaults = List.of("jazz", "rock");
boolean added = defaults.add("pop"); // UnsupportedOperationException
String replaced = defaults.set(0, "pop"); // UnsupportedOperationException
List<String> fromStream = Stream.of("jazz", "rock").toList();
boolean streamAdded = fromStream.add("pop"); // UnsupportedOperationException
List<String> view = Collections.unmodifiableList(new ArrayList<>(defaults));
boolean viewAdded = view.add("pop"); // UnsupportedOperationException
The table sums up which common collections throw the exception for which call, and which class shows up at the top of the stack trace.
| Collection | add() / remove() | set() | Top line of the stack trace |
|---|---|---|---|
| Arrays.asList(…) | throws | works | AbstractList.add |
| List.of(…), Stream.toList() | throws | throws | ImmutableCollections.uoe |
| Collections.unmodifiableList(list) | throws | throws | Collections$UnmodifiableCollection.add |
| Collections.emptyList() | throws | throws | AbstractList.add |
| Set.of(…), Map.of(…) (put()) | throws | – | ImmutableCollections.uoe |
| map.keySet(), map.values() | add() throws, remove() works | – | AbstractCollection.add |
| new ArrayList<>(…) | works | works | – |
The last but one row is a less known case. The views from keySet() and values() of a HashMap allow remove(), which removes the entry from the map, but they throw on add(), because a key alone cannot create a map entry.
1.3. Methods That Change the List Indirectly
Some methods change the list without calling add() or remove() in our code. The method Collections.sort() sorts the list in place, so it throws for a list from List.of(), even when the list is already sorted. The method removeIf() behaves differently on the two list types, as the comments show.
List<String> fixed = Arrays.asList("rock", "jazz");
Collections.sort(fixed); // works, fixed = [jazz, rock]
boolean noMatch = fixed.removeIf(g -> g.equals("pop")); // false, no exception
boolean match = fixed.removeIf(g -> g.equals("rock")); // UnsupportedOperationException: remove
List<String> constant = List.of("rock", "jazz");
Collections.sort(constant); // UnsupportedOperationException
boolean none = constant.removeIf(g -> g.equals("pop")); // UnsupportedOperationException
A list from List.of() throws on every call that could change it, whereas a list from Arrays.asList() throws only when the size would change. The second behavior hides bugs, because the code passes every test in which no element matches.
2. Resolving UnsupportedOperationException
The UnsupportedOperationException can be resolved by using a mutable collection, such as ArrayList. We should not catch the exception and go on, because it shows a bug in the code, namely a change on a list that was never meant to change.
2.1. Copy the List Into a New ArrayList
If we have unmodifiable collections, we can copy them into a mutable alternative collection class. For example, the fixed-size List in the earlier example can be passed to a new ArrayList object, a mutable collection.
List<String> genres = Arrays.asList("jazz", "rock", "pop");
List<String> playlist = new ArrayList<>(genres);
boolean added = playlist.add("soul"); // true
boolean removed = playlist.remove("jazz"); // true, playlist = [rock, pop, soul]
Here, a new ArrayList object is created from the fixed-size list returned by the Arrays.asList() method. When a new element is added to the ArrayList, it works as expected and resolves the UnsupportedOperationException. The copy is independent of the source, so the changes do not reach the original list.
2.2. Collect a Stream Into a Modifiable List
The method Stream.toList() returns an unmodifiable list. When the code changes the result, we collect with Collectors.toCollection(ArrayList::new), which is the only collector that guarantees an ArrayList. The collector Collectors.toList() returns an ArrayList in JDK 25, but its Javadoc gives no guarantee about mutability.
ArrayList<String> upper = Stream.of("jazz", "rock")
.map(String::toUpperCase)
.collect(Collectors.toCollection(ArrayList::new)); // [JAZZ, ROCK]
boolean added = upper.add("POP"); // true, upper = [JAZZ, ROCK, POP]
2.3. Use set() When the Size Stays the Same
A fixed-size list from Arrays.asList() supports set(). When the code only replaces values and never adds or removes them, set() works without a copy, as we saw in section 1.1. With indexOf() and set(), we can also replace an element in an ArrayList by its value.
2.4. Copy Lists That Come From Other Code
A method that gets a List as a parameter does not know which list class the caller used. For example, a playlist service sorts the genres it gets, and one caller passes a list from List.of(). The service fails on the first sort, so the safe version copies the parameter before it changes anything.
static List<String> sortedGenres(List<String> genres) {
List<String> copy = new ArrayList<>(genres);
Collections.sort(copy);
return copy;
}
List<String> sorted = sortedGenres(List.of("rock", "jazz")); // [jazz, rock]
The copy also protects the caller, because the method no longer changes a list it does not own. When a method returns a list that callers should not change, we return List.copyOf(list), which is a snapshot, or Collections.unmodifiableList(list), which is a read-only view, as section 5.3 shows.
3. Finding the List That Caused the Exception
The exception message is empty for most collections, so the stack trace is the main clue. The top frames name the class that threw the exception, and the class tells us how the list was created.
- ImmutableCollections.uoe means a list, set or map from List.of(), Set.of(), Map.of(), List.copyOf() or Stream.toList().
- AbstractList.add or AbstractList.remove means a fixed-size list such as the one from Arrays.asList(), or another list class that does not override the method.
- Collections$UnmodifiableCollection means a read-only view from Collections.unmodifiableList() or a similar wrapper.
For the List.of() case, the first line of our own code, UnsupportedOperationExceptionExample.java:43, is the call that tried to change the list, and from there we follow the variable back to the place where the list was created.
java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:159)
at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:164)
at com.howtodoinjava.core.collections.list.unsupported.UnsupportedOperationExceptionExample.stackTraces(UnsupportedOperationExceptionExample.java:43)
at com.howtodoinjava.core.collections.list.unsupported.UnsupportedOperationExceptionExample.main(UnsupportedOperationExceptionExample.java:20)
4. Throwing UnsupportedOperationException in Our Own Code
We throw the exception ourselves when a class implements an interface but cannot support one of its methods. For example, a read-only playlist class implements a Playlist interface, and the interface has a delete() method that a read-only playlist must not run.
@Override
public void delete(String genre) {
throw new UnsupportedOperationException("Read-only playlist cannot delete " + genre);
}
A clear message names the operation and the reason, which the JDK collections do not do. When most methods of an interface are unsupported, the interface is too large, and a smaller interface with only the supported methods is the better design.
5. UnsupportedOperationException FAQs
5.1. Is UnsupportedOperationException a Checked Exception?
No. It extends RuntimeException, so the compiler does not force us to catch or declare it. It is in the java.lang package, so it needs no import.
5.2. Why Does Arrays.asList().add() Throw UnsupportedOperationException?
The list from Arrays.asList() is backed by the array that we pass, and the array has a fixed length. Adding or removing an element would change the length, so the list throws the exception, as we saw in section 1.1. We copy the list into a new ArrayList before we add elements.
5.3. What Is the Difference Between an Unmodifiable and an Immutable List?
An unmodifiable view from Collections.unmodifiableList() blocks changes through the view, but it shows the changes that other code makes to the source list. A list from List.of() or List.copyOf() has no source list, so its content never changes after creation.
List<String> source = new ArrayList<>(List.of("jazz"));
List<String> view = Collections.unmodifiableList(source);
List<String> copy = List.copyOf(source);
source.add("rock"); // view = [jazz, rock], copy = [jazz]
5.4. Should We Catch UnsupportedOperationException?
No. The exception shows that the code changes a list that does not allow changes, so we fix the code with a modifiable copy, as shown in section 2. Catching the exception would hide the bug and lose the change.
6. Conclusion
The UnsupportedOperationException is an unchecked exception that collections throw for optional operations they do not support. Lists from Arrays.asList() have a fixed size and reject add() and remove(), whereas unmodifiable lists, such as the ones from List.of() or Stream.toList(), reject every change.
To resolve the exception, we copy the values into a new ArrayList, or collect a stream with Collectors.toCollection(ArrayList::new). The top frames of the stack trace tell us which kind of list caused it.
7. References
- UnsupportedOperationException JavaDoc (Java 25)
- List JavaDoc (Java 25)
- Arrays JavaDoc (Java 25)
- Collections JavaDoc (Java 25)
- Collections Framework Overview (Java 25)
Happy Learning !!