Java Collections Framework

The Java collections framework has 25+ years of history. The core types are stable; the modern additions (immutable factories, sequenced collections) round out long-standing gaps. This page is the practical guide — which collection to pick when, the immutable variants, and the patterns that have aged well.

The core types

List

Ordered, allows duplicates, indexed access.

ImplementationWhen to use
ArrayListDefault. Backed by array; fast random access; slow inserts in middle
LinkedListAlmost never. Doubly-linked list; rarely the right answer
CopyOnWriteArrayListRead-heavy concurrent access; writes copy the entire array

Default to ArrayList. The cases for the others are narrow.

Set

Unordered (or ordered), no duplicates.

ImplementationWhen to use
HashSetDefault. Hash-based, no order
LinkedHashSetInsertion-order iteration matters
TreeSetNatural-order or custom-order iteration
ConcurrentSkipListSetConcurrent ordered set
EnumSetSet of enum values; bit-vector backed; very fast

HashSet is the right default for most cases. LinkedHashSet if you need to preserve insertion order.

Map

Key-value pairs, unique keys.

ImplementationWhen to use
HashMapDefault. Fast; no ordering
LinkedHashMapInsertion-order or access-order matters
TreeMapSorted by key
ConcurrentHashMapConcurrent map (the right concurrent map)
EnumMapMap keyed by enum; very fast

HashMap for general use. ConcurrentHashMap for shared mutable maps across threads. EnumMap when keyed by enum (faster, less memory).

Queue / Deque

ImplementationWhen to use
ArrayDequeDefault for stack/queue use; faster than LinkedList
PriorityQueueHeap-based priority queue
LinkedBlockingQueueProducer-consumer with blocking semantics
ConcurrentLinkedQueueConcurrent queue, non-blocking

ArrayDeque for non-concurrent stack/queue. The Stack class is legacy; do not use it.

Immutable factories (Java 9+)

List<String> names = List.of("Alice", "Bob", "Carol");
Set<Integer> ports = Set.of(80, 443, 8080);
Map<String, String> headers = Map.of("Content-Type", "application/json");

The result is genuinely immutable — modification throws UnsupportedOperationException. These are the right default for "I have a small fixed collection."

For larger or computed immutable collections:

List<X> result = stream.collect(Collectors.toUnmodifiableList());
// or in Java 16+
List<X> result = stream.toList();

Sequenced collections (Java 21+)

Long-standing API gaps filled. SequencedCollection, SequencedSet, SequencedMap interfaces add:

Implemented by List, LinkedHashSet, LinkedHashMap, Deque, etc. Removes the need for awkward iterator().next() patterns to access the first element.

Iteration patterns

Enhanced for loop (default)

for (String name : names) {
    process(name);
}

Almost always the right way to iterate when you don't need the index.

Iterator (when you need to remove during iteration)

Iterator<String> it = names.iterator();
while (it.hasNext()) {
    if (shouldRemove(it.next())) {
        it.remove();
    }
}

Index-based (when you need the index)

for (int i = 0; i < names.size(); i++) {
    System.out.println(i + ": " + names.get(i));
}

Streams (for transformation/aggregation)

names.stream()
    .filter(n -> n.length() > 5)
    .forEach(this::process);

See JavaStreamsAndFunctionalProgramming.

Common operations and their costs

OperationArrayListLinkedListHashMapTreeMap
Get by indexO(1)O(n)n/an/a
Get by keyn/an/aO(1) avgO(log n)
Insert at endO(1) amortizedO(1)O(1) avgO(log n)
Insert at frontO(n)O(1)n/an/a
Remove by valueO(n)O(n)O(1) avgO(log n)
IterationO(n)O(n)O(n)O(n) ordered

The "amortized" on ArrayList insert: occasionally an internal array resize is O(n), averaged out across many inserts.

Memory and capacity

Pre-sizing collections has a real performance impact when sizes are known and large.

Thread safety

Default collections are not thread-safe. Three options for concurrent access:

  1. Concurrent collections (ConcurrentHashMap, CopyOnWriteArrayList, ConcurrentLinkedQueue) — designed for concurrent access
  2. Synchronized wrappers (Collections.synchronizedXxx) — single-lock approach; simple but contention can be high
  3. Immutable collections — no synchronization needed; readers see a consistent snapshot

For maps shared across threads, ConcurrentHashMap is almost always the right choice.

Common failure patterns

Further Reading