Common Pitfalls When Using Java List Implementations and How to Avoid Them
This article systematically examines ten typical pitfalls encountered when converting arrays to lists, performing add/remove operations, using subList, handling memory consumption, and working with thread‑safe collections such as CopyOnWriteArrayList in Java, and provides concrete code‑level solutions and performance recommendations.
1. Arrays.asList with Primitive Arrays
When converting a primitive array to a List using Arrays.asList , the resulting list contains a single element because the whole primitive array is treated as one object.
int[] arr = {1, 2, 3};
List list = Arrays.asList(arr);
System.out.println(list.size()); // 1Solution 1: Use Java 8 streams to box the primitives.
List
collect = Arrays.stream(arr).boxed().collect(Collectors.toList());
System.out.println(collect.size()); // 3
System.out.println(collect.get(0).getClass()); // class java.lang.IntegerSolution 2: Declare the array with wrapper types.
Integer[] integerArr = {1, 2, 3};
List
integerList = Arrays.asList(integerArr);
System.out.println(integerList.size()); // 3
System.out.println(integerList.get(0).getClass()); // class java.lang.Integer2. Immutable List Returned by Arrays.asList
The list returned by Arrays.asList does not support structural modifications because it is backed by an internal ArrayList class that extends AbstractList without implementing add and remove .
private static void asListAdd() {
String[] arr = {"1", "2", "3"};
List
strings = new ArrayList<>(Arrays.asList(arr));
arr[2] = "4";
System.out.println(strings.toString());
Iterator
iterator = strings.iterator();
while (iterator.hasNext()) {
if ("4".equals(iterator.next())) {
iterator.remove();
}
}
strings.forEach(val -> {
strings.remove("4");
strings.add("3");
});
System.out.println(Arrays.asList(arr).toString());
}
// Throws UnsupportedOperationExceptionWrap the result in a new ArrayList to obtain a mutable list.
List
mutable = new ArrayList<>(Arrays.asList(arr));3. Modifying the Original Array Affects the List
The list created by Arrays.asList holds a reference to the original array, so any change to the array is reflected in the list.
public static
List
asList(T... a) {
return new ArrayList<>(a);
}
ArrayList(E[] array) { a = Objects.requireNonNull(array); }Solution: Create a new ArrayList from the returned list to break the reference.
List
independent = new ArrayList<>(Arrays.asList(arr));4. java.util.ArrayList May Still Appear Immutable
Even when wrapping the Arrays.asList result with new ArrayList<>(...) , the underlying list may still throw UnsupportedOperationException if the internal implementation does not support add / remove . The root cause is the same internal ArrayList class that lacks those methods.
5. SubList Cast to ArrayList Causes ClassCastException
Calling list.subList(...) returns an internal SubList view, not a true ArrayList . Casting it to ArrayList results in a ClassCastException .
List
names = new ArrayList
() {{ add("one"); add("two"); add("three"); }};
ArrayList strings = (ArrayList) names.subList(0, 1); // throwsSolution: Use a new ArrayList to copy the sub‑list.
List
copy = new ArrayList<>(names.subList(0, 1));6. SubList Can Lead to OOM
Because a sub‑list holds a reference to the original large list, repeatedly creating sub‑lists in a loop prevents the original list from being garbage‑collected, eventually causing OutOfMemoryError .
private static void subListOomTest() {
IntStream.range(0, 1000).forEach(i -> {
List
collect = IntStream.range(0, 100000).boxed().collect(Collectors.toList());
data.add(collect.subList(0, 1));
});
// OOMSolution: Copy the sub‑list into a new container or use stream skip / limit for slicing.
List
list = new ArrayList<>(collect.subList(0, 1));
List
list2 = collect.stream().skip(0).limit(1).collect(Collectors.toList());7. LinkedList Insertion Not Always Faster Than ArrayList
Performance tests inserting 100 000 random elements show that LinkedList can be significantly slower than ArrayList because each insertion requires node traversal.
private static void test() {
StopWatch sw = new StopWatch();
int elementCount = 100000;
sw.start("ArrayList add");
List
arrayList = IntStream.rangeClosed(1, elementCount).boxed()
.collect(Collectors.toCollection(ArrayList::new));
IntStream.rangeClosed(0, elementCount).forEach(i ->
arrayList.add(ThreadLocalRandom.current().nextInt(elementCount), 1));
sw.stop();
sw.start("LinkedList add");
List
linkedList = IntStream.rangeClosed(1, elementCount).boxed()
.collect(Collectors.toCollection(LinkedList::new));
IntStream.rangeClosed(0, elementCount).forEach(i ->
linkedList.add(ThreadLocalRandom.current().nextInt(elementCount), 1));
sw.stop();
System.out.println(sw.prettyPrint());
}Conclusion: Use LinkedList only for head/tail operations; otherwise prefer ArrayList after benchmarking.
8. CopyOnWriteArrayList High Memory Overhead
Each write creates a fresh copy of the internal array, doubling memory usage and causing frequent GC, especially with large collections.
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}9. CopyOnWriteArrayList Iterator Weak Consistency
The iterator works on a snapshot taken at creation time; modifications after that are invisible.
public Iterator
iterator() {
return new COWIterator
(getArray(), 0);
}
static final class COWIterator
implements ListIterator
{
private final Object[] snapshot;
private int cursor;
// ... hasNext, next, etc.
}Demo shows that changes made in another thread are not reflected when iterating over the original iterator.
10. CopyOnWriteArrayList Iterator Does Not Support Modification
The iterator’s remove , add and set methods always throw UnsupportedOperationException because the iterator traverses a read‑only snapshot.
public void remove() { throw new UnsupportedOperationException(); }
public void add(E e) { throw new UnsupportedOperationException(); }
public void set(E e) { throw new UnsupportedOperationException(); }Summary
The article highlights frequent pitfalls when using Java collection utilities such as Arrays.asList , ArrayList , LinkedList , subList , and CopyOnWriteArrayList . Understanding these behaviors helps developers choose the right data structure, avoid hidden bugs, memory leaks, and performance regressions.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.