Fundamentals 20 min read

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.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Common Pitfalls When Using Java List Implementations and How to Avoid Them

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()); // 1

Solution 1: Use Java 8 streams to box the primitives.

List<Integer> collect = Arrays.stream(arr).boxed().collect(Collectors.toList());
System.out.println(collect.size()); // 3
System.out.println(collect.get(0).getClass()); // class java.lang.Integer

Solution 2: Declare the array with wrapper types.

Integer[] integerArr = {1, 2, 3};
List<Integer> integerList = Arrays.asList(integerArr);
System.out.println(integerList.size()); // 3
System.out.println(integerList.get(0).getClass()); // class java.lang.Integer

2. 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<String> strings = new ArrayList<>(Arrays.asList(arr));
    arr[2] = "4";
    System.out.println(strings.toString());
    Iterator<String> 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 UnsupportedOperationException

Wrap the result in a new ArrayList to obtain a mutable list.

List<String> 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 <T> List<T> 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<String> 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<String> names = new ArrayList<String>() {{ add("one"); add("two"); add("three"); }};
ArrayList strings = (ArrayList) names.subList(0, 1); // throws

Solution: Use a new ArrayList to copy the sub‑list.

List<String> 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<Integer> collect = IntStream.range(0, 100000).boxed().collect(Collectors.toList());
        data.add(collect.subList(0, 1));
    });
    // OOM

Solution: Copy the sub‑list into a new container or use stream skip / limit for slicing.

List<Integer> list = new ArrayList<>(collect.subList(0, 1));
List<Integer> 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<Integer> 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<Integer> 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<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}
static final class COWIterator<E> implements ListIterator<E> {
    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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

concurrencyListArrayListCopyOnWriteArrayListLinkedList
Java Architect Essentials
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.