Back to Blog
Java

Java CopyOnWriteArrayList: Usage and Tradeoffs

java copyonwritearraylist: Learn how CopyOnWriteArrayList works, its iteration semantics, and when to use it in concurrent Java applications.

JavaConcurrent CollectionsCopyOnWriteArrayListThread SafetyPerformanceCollections Framework
Illustration of a list being copied on write operation in Java concurrency.

java copyonwritearraylist requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

What CopyOnWriteArrayList Is and How It Works

CopyOnWriteArrayList is a thread-safe implementation of the List interface that stores its elements in an array. The key difference from other list implementations is that every mutating operation—add, set, remove, and similar—creates a fresh copy of the underlying array. The original array remains unchanged until the new copy is fully constructed and then atomically assigned as the current backing array.

This design means that readers never need locks. A read operation, such as get or size, simply accesses the current array reference without synchronization. Because the array is never modified in place, a reader sees a consistent snapshot of the list as of the time it obtained the array reference.

The class is part of java.util.concurrent and is available since Java 5. It is designed for scenarios where reads are far more frequent than writes, and where the list is shared across multiple threads.

The Copy-on-Write Mechanism in Practice

When you call add on a CopyOnWriteArrayList, the implementation performs the following steps:

  1. Acquires a lock to ensure only one writer can modify the list at a time.
  2. Copies the existing array into a new array with increased length.
  3. Adds the new element at the end of the new array.
  4. Replaces the current array reference with the new array.
  5. Releases the lock.

The same pattern applies to remove, set, and other mutators. Each write operation allocates a new array and copies all existing elements. This is why writes are expensive relative to a standard ArrayList, which can modify the backing array in place.

Here is a minimal example that demonstrates the behavior:

import java.util.concurrent.CopyOnWriteArrayList; public class CopyOnWriteExample { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.add("one"); list.add("two"); for (String s : list) { System.out.println(s); list.add("three"); // This is allowed during iteration } System.out.println("Size: " + list.size()); } }

In this example, the iteration over the list continues even though an element is added inside the loop. The iterator sees the original array snapshot, so it does not throw a ConcurrentModificationException. The new element appears in the list after the iteration completes, as the final size output shows.

Iteration Semantics and Weak Consistency

Iterators returned by CopyOnWriteArrayList are snapshot-based. When you create an iterator, it captures a reference to the current array. Subsequent writes to the list do not affect that iterator. The iterator will not see elements added, removed, or changed after its creation. This behavior is often called weakly consistent.

This is different from the fail-fast iterators used by ArrayList and HashMap, which throw ConcurrentModificationException when the collection is structurally modified during iteration. With CopyOnWriteArrayList, concurrent modification is safe, but the iterator does not reflect the latest state.

The snapshot behavior is useful in event listener lists or observer registries where you want to notify a consistent set of listeners at the moment you start iterating. If a listener is added or removed while notifications are being sent, the current iteration remains unaffected.

When to Use CopyOnWriteArrayList

CopyOnWriteArrayList is a good choice when your application has a list that is read frequently but modified rarely. Common examples include:

  • A list of registered listeners or observers in an event system.
  • A configuration list that is updated infrequently but accessed on every request.
  • A cache of immutable objects that is replaced occasionally.

The class is not suitable for write-heavy workloads. If your code performs many add and remove operations, the constant copying will degrade performance and increase memory allocation pressure. In such cases, a synchronized list or a concurrent collection like ConcurrentLinkedDeque may be more appropriate.

You should also avoid using CopyOnWriteArrayList when you need index-based access to a large list that changes frequently, because every modification requires copying the entire array.

Performance and Memory Tradeoffs

The primary performance advantage of CopyOnWriteArrayList is that reads are lock-free and extremely fast. There is no synchronization overhead, no contention, and no blocking. This makes it ideal for high-read, low-write concurrency.

The tradeoff is the cost of writes. Each mutation copies the entire backing array. For a list of n elements, every add or remove operation has O(n) time complexity. In contrast, ArrayList's add at the end is amortized O(1), and remove by index is O(n) but does not copy the whole array on every operation.

Memory usage also increases because each write creates a new array while the old array may still be referenced by iterators or other readers. If many writes occur, the garbage collector has to reclaim the old arrays. This can lead to higher memory consumption and more frequent GC pauses in applications with large lists.

The following table compares the key characteristics of CopyOnWriteArrayList with ArrayList and a synchronized list:

CharacteristicCopyOnWriteArrayListArrayListCollections.synchronizedList
Thread safetyYesNoYes
Read performanceVery fast (no locks)FastSlower (lock on every access)
Write performanceO(n) per mutationO(1) amortized addO(1) with lock overhead
Iterator behaviorSnapshot, weakly consistentFail-fastFail-fast (if using default iterator)
Memory overheadHigh on writesLowLow

This table gives a quick overview, but the actual choice depends on your specific access patterns and concurrency requirements.

Alternatives and Comparison with Other List Implementations

When you need a thread-safe list, you have several options beyond CopyOnWriteArrayList.

  • Collections.synchronizedList(new ArrayList<>()) wraps a regular list with synchronized methods. It is simple but requires external synchronization for compound operations like iteration, because the iterator is not thread-safe.
  • ConcurrentLinkedDeque is a thread-safe deque that does not copy on write, but it does not implement the List interface and does not support index-based access.
  • CopyOnWriteArrayList is the only thread-safe List implementation that provides snapshot iterators and lock-free reads.

For read-heavy scenarios, CopyOnWriteArrayList is often the best fit. For write-heavy scenarios, a synchronized list may be sufficient if you can manage locking externally. If you need a concurrent queue or stack, consider ConcurrentLinkedDeque.

The decision should be based on the ratio of reads to writes, the size of the list, and whether snapshot iteration is a requirement.

Common Pitfalls and Misconceptions

One common mistake is assuming that CopyOnWriteArrayList is always faster than a synchronized list. That is only true when reads dominate. If writes are frequent, the copying overhead can make it slower and cause memory churn.

Another pitfall is using the iterator to modify the list. The iterator's remove method is not supported and throws UnsupportedOperationException. If you need to remove elements while iterating, you must collect the elements to remove and call remove on the list after the iteration, or use a different collection.

Also, note that the size method reflects the current array at the time of the call. If another thread is writing, the size you see may be stale. This is consistent with the weakly consistent nature of the class.

Finally, do not use CopyOnWriteArrayList for very large lists that are updated regularly. The O(n) copy cost on every write will become a bottleneck. In such cases, consider partitioning the data or using a different concurrency strategy.

The class is a specialized tool. Understanding its internal copy-on-write mechanism helps you decide when it is the right choice and when it will cause more problems than it solves.

java copyonwritearraylist: Practical Usage and Code Examples | RYUSLOG DEV