Java Collections Framework
A complete guide to Java Collections Framework — covering the full hierarchy, List, Set, Map, Queue interfaces, ArrayList, LinkedList, HashMap, TreeMap, Iterator, Comparable vs Comparator, thread-safe collections, Java 21 Sequenced Collections, and best practices with real-world examples.
Last Updated
March 2026
Read Time
22 min
Level
Beginner to Intermediate
What is the Java Collections Framework?
The Java Collections Framework (JCF) is a unified architecture introduced in Java 2 (JDK 1.2) for representing and manipulating groups of objects. It provides a complete set of interfaces, implementation classes, and algorithms that allow developers to store, retrieve, sort, search, and manipulate data efficiently — without reinventing the wheel for every project.
Before the Collections Framework, Java developers had to use arrays (fixed-size, no built-in utilities), Vector, and Hashtable — all of which were inconsistent, verbose, and poorly designed. The JCF replaced this fragmentation with a clean, consistent API backed by well-studied computer science data structures.
The entire framework lives in the java.util package. At its core are four primary interfaces: List (ordered, duplicates allowed), Set (no duplicates), Map (key-value pairs), and Queue (FIFO ordering). Every collection class you will ever use is an implementation of one of these interfaces.
Why Use Collections Instead of Arrays?
You might wonder — "Java already has arrays. Why do I need Collections?" Here's why Collections are almost always the better choice in real-world development:
Java Collections Hierarchy
Understanding the hierarchy is the most important first step. Java Collections are organized around two separate root interfaces — Collection and Map — because maps store key-value pairs and are fundamentally different from single-element collections.
Key insight: Map does NOT extend Collection. This is intentional — maps are fundamentally key-value structures, not simple element containers. However, you can get a Collection view of a map using map.values(), map.keySet(), and map.entrySet().
Collection vs Collections — Critical Difference
One of the most common beginner confusions — Collection vs Collections. They are completely different things:
List Interface — Ordered Collections with Duplicates
The List interface extends Collection and represents an ordered sequence of elements. Lists maintain insertion order, allow duplicate elements, and support index-based access. It is the most widely used collection interface in Java.
- ▶
Ordered — Elements maintain the order in which they were inserted.
- ▶
Index-based access — Elements can be accessed by their position using get(int index), set(int index, E element).
- ▶
Duplicates allowed — The same element can appear multiple times.
- ▶
Null elements allowed — Most List implementations accept null values.
import java.util.*;
public class ListDemo {
public static void main(String[] args) {
List<String> cities = new ArrayList<>();
// Adding elements
cities.add("Mumbai");
cities.add("Delhi");
cities.add("Bangalore");
cities.add("Mumbai"); // duplicate — allowed in List
cities.add(1, "Chennai"); // insert at index 1
System.out.println(cities); // [Mumbai, Chennai, Delhi, Bangalore, Mumbai]
System.out.println(cities.get(2)); // Delhi
System.out.println(cities.size()); // 5
System.out.println(cities.contains("Delhi")); // true
System.out.println(cities.indexOf("Mumbai")); // 0
// Remove by index vs by object
cities.remove(0); // removes element at index 0
cities.remove("Bangalore"); // removes first occurrence of object
// Iterate with enhanced for-loop
for (String city : cities) {
System.out.println(city);
}
// Sublist
List<String> sub = cities.subList(0, 2);
System.out.println("Sublist: " + sub);
}
}Output
[Mumbai, Chennai, Delhi, Bangalore, Mumbai] Delhi 5 true 0 Chennai Delhi Mumbai Sublist: [Chennai, Delhi]ArrayList vs LinkedList — When to Use Which?
Both ArrayList and LinkedList implement the List interface, but they use fundamentally different internal data structures — leading to very different performance characteristics. Choosing the right one is a key engineering decision.
import java.util.*;
public class ListComparison {
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();
// Populate both lists
for (int i = 0; i < 100000; i++) {
arrayList.add(i);
linkedList.add(i);
}
// Random access — ArrayList wins
long start = System.nanoTime();
arrayList.get(50000);
System.out.println("ArrayList get: " + (System.nanoTime() - start) + " ns");
start = System.nanoTime();
linkedList.get(50000);
System.out.println("LinkedList get: " + (System.nanoTime() - start) + " ns");
// Insert at beginning — LinkedList wins
start = System.nanoTime();
arrayList.add(0, -1);
System.out.println("ArrayList add(0): " + (System.nanoTime() - start) + " ns");
start = System.nanoTime();
((LinkedList<Integer>) linkedList).addFirst(-1);
System.out.println("LinkedList addFirst: " + (System.nanoTime() - start) + " ns");
}
}Set Interface — No Duplicates Guaranteed
The Set interface extends Collection and represents a collection that contains no duplicate elements. It models the mathematical set abstraction. Set does not guarantee any particular iteration order (depends on implementation). Attempting to add a duplicate element simply returns false — no exception is thrown.
import java.util.*;
public class SetDemo {
public static void main(String[] args) {
// HashSet — no order
Set<String> hashSet = new HashSet<>();
hashSet.add("Mango");
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Apple"); // duplicate — silently ignored
System.out.println("HashSet: " + hashSet);
// LinkedHashSet — insertion order preserved
Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("Mango");
linkedSet.add("Apple");
linkedSet.add("Banana");
linkedSet.add("Apple"); // duplicate — ignored
System.out.println("LinkedHashSet: " + linkedSet);
// TreeSet — sorted alphabetically
Set<String> treeSet = new TreeSet<>();
treeSet.add("Mango");
treeSet.add("Apple");
treeSet.add("Banana");
treeSet.add("Apple"); // duplicate — ignored
System.out.println("TreeSet: " + treeSet);
// Set operations
System.out.println("Contains Apple: " + hashSet.contains("Apple"));
System.out.println("Size: " + hashSet.size());
}
}Output
HashSet: [Apple, Mango, Banana] LinkedHashSet: [Mango, Apple, Banana] TreeSet: [Apple, Banana, Mango] Contains Apple: true Size: 3Map Interface — Key-Value Pair Storage
The Map interface represents a collection of key-value pairs where each key is unique. Map does NOT extend the Collection interface. It is the go-to data structure when you need to look up values by a meaningful key — like a dictionary, registry, or cache.
- ▶
Keys must be unique — Inserting a duplicate key overwrites the existing value (no exception).
- ▶
Values can be duplicate — Multiple keys can map to the same value.
- ▶
No index access — Elements are accessed by key, not by position.
- ▶
Three views — keySet() returns all keys as a Set; values() returns all values as a Collection; entrySet() returns all key-value pairs as Set<Map.Entry<K,V>>.
import java.util.*;
public class MapDemo {
public static void main(String[] args) {
Map<String, Integer> scores = new HashMap<>();
// put — add or overwrite
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Charlie", 92);
scores.put("Alice", 98); // overwrites existing key
// get
System.out.println(scores.get("Alice")); // 98
System.out.println(scores.get("Unknown")); // null
// getOrDefault — safe fallback
System.out.println(scores.getOrDefault("Zara", 0)); // 0
// containsKey / containsValue
System.out.println(scores.containsKey("Bob")); // true
System.out.println(scores.containsValue(100)); // false
// putIfAbsent — only puts if key not present
scores.putIfAbsent("Bob", 50); // Bob already exists — no change
scores.putIfAbsent("Diana", 88);
// Iterate over entries
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.println(entry.getKey() + " → " + entry.getValue());
}
// remove
scores.remove("Charlie");
System.out.println("Size: " + scores.size());
}
}Output
98 null 0 true false Alice → 98 Bob → 87 Charlie → 92 Diana → 88 Size: 3HashMap vs TreeMap vs LinkedHashMap
Choosing the right Map implementation is crucial for both correctness and performance. Here is a detailed comparison of the three most commonly used Map implementations:
import java.util.*;
public class MapOrderDemo {
public static void main(String[] args) {
// HashMap — unpredictable order
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("Banana", 2);
hashMap.put("Apple", 1);
hashMap.put("Mango", 3);
System.out.println("HashMap: " + hashMap);
// LinkedHashMap — insertion order preserved
Map<String, Integer> linkedMap = new LinkedHashMap<>();
linkedMap.put("Banana", 2);
linkedMap.put("Apple", 1);
linkedMap.put("Mango", 3);
System.out.println("LinkedHashMap: " + linkedMap);
// TreeMap — sorted by key (alphabetical)
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("Banana", 2);
treeMap.put("Apple", 1);
treeMap.put("Mango", 3);
System.out.println("TreeMap: " + treeMap);
// TreeMap range operations
TreeMap<String, Integer> tm = new TreeMap<>(treeMap);
System.out.println("First key: " + tm.firstKey());
System.out.println("Last key: " + tm.lastKey());
System.out.println("HeadMap (< Mango): " + tm.headMap("Mango"));
}
}Output
HashMap: {Apple=1, Mango=3, Banana=2} LinkedHashMap: {Banana=2, Apple=1, Mango=3} TreeMap: {Apple=1, Banana=2, Mango=3} First key: Apple Last key: Mango HeadMap (< Mango): {Apple=1, Banana=2}Queue and Deque Interface
The Queue interface represents a collection designed for FIFO (First-In, First-Out) element processing. The Deque (Double-Ended Queue) interface extends Queue and supports element insertion and removal at both ends.
import java.util.*;
public class QueueDemo {
public static void main(String[] args) {
// ArrayDeque as Queue (FIFO)
Queue<String> queue = new ArrayDeque<>();
queue.offer("Task-1");
queue.offer("Task-2");
queue.offer("Task-3");
System.out.println("Head: " + queue.peek()); // Task-1 (no remove)
System.out.println("Poll: " + queue.poll()); // Task-1 (removes)
System.out.println("Queue: " + queue);
// ArrayDeque as Stack (LIFO)
Deque<String> stack = new ArrayDeque<>();
stack.push("Page-1");
stack.push("Page-2");
stack.push("Page-3");
System.out.println("Peek: " + stack.peek()); // Page-3
System.out.println("Pop: " + stack.pop()); // Page-3
System.out.println("Stack: " + stack);
// PriorityQueue — min-heap
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(40);
pq.offer(10);
pq.offer(30);
pq.offer(20);
System.out.print("PriorityQueue poll order: ");
while (!pq.isEmpty()) {
System.out.print(pq.poll() + " "); // 10 20 30 40
}
}
}Output
Head: Task-1 Poll: Task-1 Queue: [Task-2, Task-3] Peek: Page-3 Pop: Page-3 Stack: [Page-2, Page-1] PriorityQueue poll order: 10 20 30 40Iterator and ListIterator
The Iterator interface provides a standard way to traverse any collection without exposing the underlying data structure. It is the correct way to safely remove elements while iterating — using a regular for-loop and calling remove() directly on the collection causes a ConcurrentModificationException.
import java.util.*;
public class IteratorDemo {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8));
// Remove all even numbers safely using Iterator
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
int num = it.next();
if (num % 2 == 0) {
it.remove(); // safe removal — no ConcurrentModificationException
}
}
System.out.println("After removing evens: " + numbers);
// ListIterator — bidirectional traversal
List<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Mango", "Banana"));
ListIterator<String> lit = fruits.listIterator(fruits.size()); // start at end
System.out.print("Reverse order: ");
while (lit.hasPrevious()) {
System.out.print(lit.previous() + " ");
}
}
}Output
After removing evens: [1, 3, 5, 7] Reverse order: Banana Mango AppleComparable vs Comparator — Sorting Collections
To sort custom objects in Java collections, you need to define an ordering. Java provides two interfaces for this: Comparable (natural ordering, built into the class) and Comparator (external custom ordering, defined separately).
import java.util.*;
// Comparable — natural order by salary
class Employee implements Comparable<Employee> {
String name;
int salary;
String department;
Employee(String name, int salary, String department) {
this.name = name;
this.salary = salary;
this.department = department;
}
@Override
public int compareTo(Employee other) {
return Integer.compare(this.salary, other.salary); // natural: by salary asc
}
@Override
public String toString() { return name + "(" + salary + ")"; }
}
public class SortingDemo {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>(Arrays.asList(
new Employee("Alice", 75000, "Engineering"),
new Employee("Bob", 55000, "Marketing"),
new Employee("Charlie", 90000, "Engineering"),
new Employee("Diana", 65000, "HR")
));
// Natural order (Comparable) — by salary
Collections.sort(employees);
System.out.println("By salary (asc): " + employees);
// Comparator — by name
employees.sort(Comparator.comparing(e -> e.name));
System.out.println("By name: " + employees);
// Comparator — by salary desc, then name asc
employees.sort(Comparator.comparingInt((Employee e) -> e.salary)
.reversed()
.thenComparing(e -> e.name));
System.out.println("Salary desc+name:" + employees);
}
}Output
By salary (asc): [Bob(55000), Diana(65000), Alice(75000), Charlie(90000)] By name: [Alice(75000), Bob(55000), Charlie(90000), Diana(65000)] Salary desc+name:[Charlie(90000), Alice(75000), Diana(65000), Bob(55000)]Generics in Collections — Type Safety
Generics (introduced in Java 5) are the reason you write List<String> instead of just List. Without generics, collections stored elements as Object, requiring explicit casting and risking ClassCastException at runtime. Generics move type safety to compile time.
import java.util.*;
public class GenericsDemo {
public static void main(String[] args) {
// ❌ Raw type (pre-Java 5 style) — unsafe
List rawList = new ArrayList();
rawList.add("Hello");
rawList.add(42); // no compile error!
String s = (String) rawList.get(1); // ClassCastException at RUNTIME
// ✅ Generic type — compile-time safety
List<String> typedList = new ArrayList<>();
typedList.add("Hello");
typedList.add("World");
// typedList.add(42); // ✅ COMPILE ERROR — caught early
String safe = typedList.get(0); // no cast needed
System.out.println(safe);
// Generic method — works with any type
printAll(typedList);
printAll(List.of(1, 2, 3, 4));
}
// Wildcard <?> — accepts any type of List
static void printAll(List<?> list) {
for (Object item : list) {
System.out.print(item + " ");
}
System.out.println();
}
}Output
Hello Hello World 1 2 3 4Java 21 — Sequenced Collections (New!)
Java 21 (JEP 431) introduced three new interfaces — SequencedCollection, SequencedSet, and SequencedMap — that add first/last element access to existing collection types. Before Java 21, accessing the last element of a list required the ugly workaround list.get(list.size() - 1). Sequenced Collections solve this cleanly.
import java.util.*;
public class SequencedDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>(List.of("Alpha", "Beta", "Gamma", "Delta"));
// Java 21 — clean first/last access
System.out.println("First: " + list.getFirst()); // Alpha
System.out.println("Last: " + list.getLast()); // Delta
// Before Java 21 (ugly workaround)
// String last = list.get(list.size() - 1);
list.addFirst("Zeta");
list.addLast("Omega");
System.out.println("After addFirst/addLast: " + list);
list.removeFirst();
list.removeLast();
System.out.println("After removeFirst/Last: " + list);
// reversed() — returns a view, no copy
System.out.println("Reversed view: " + list.reversed());
// SequencedMap — LinkedHashMap
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
System.out.println("First entry: " + map.firstEntry());
System.out.println("Last entry: " + map.lastEntry());
System.out.println("Reversed map: " + map.reversed());
}
}Output
First: Alpha Last: Delta After addFirst/addLast: [Zeta, Alpha, Beta, Gamma, Delta, Omega] After removeFirst/Last: [Alpha, Beta, Gamma, Delta] Reversed view: [Delta, Gamma, Beta, Alpha] First entry: A=1 Last entry: C=3 Reversed map: {C=3, B=2, A=1}Thread-Safe Collections
Standard collections like ArrayList, HashMap, and HashSet are not thread-safe. In multi-threaded environments, concurrent access without synchronization causes data corruption and ConcurrentModificationException. Java provides several thread-safe alternatives:
import java.util.concurrent.*;
import java.util.*;
public class ThreadSafeDemo {
public static void main(String[] args) throws InterruptedException {
// ConcurrentHashMap — safe for concurrent read/write
ConcurrentHashMap<String, Integer> wordCount = new ConcurrentHashMap<>();
Runnable task = () -> {
for (String word : new String[]{"java", "python", "java", "kotlin", "java"}) {
wordCount.merge(word, 1, Integer::sum); // atomic increment
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("Word counts: " + wordCount);
// CopyOnWriteArrayList — safe for iteration under concurrent modification
CopyOnWriteArrayList<String> safeList = new CopyOnWriteArrayList<>();
safeList.add("Alpha");
safeList.add("Beta");
// Iterating will NOT throw ConcurrentModificationException
for (String s : safeList) {
safeList.add("Gamma"); // safe — modifies a copy
System.out.println("Read: " + s);
break; // prevent infinite loop in demo
}
}
}Collections Utility Class — Essential Methods
The java.util.Collections class provides powerful static utility methods for working with collection objects. These are some of the most frequently used methods in real-world Java development:
import java.util.*;
public class CollectionsUtilDemo {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>(Arrays.asList(5, 3, 8, 1, 9, 2, 7));
// sort — natural order
Collections.sort(nums);
System.out.println("Sorted: " + nums);
// reverse
Collections.reverse(nums);
System.out.println("Reversed: " + nums);
// shuffle — random order
Collections.shuffle(nums);
System.out.println("Shuffled: " + nums);
// min / max
System.out.println("Min: " + Collections.min(nums));
System.out.println("Max: " + Collections.max(nums));
// frequency — count occurrences
List<String> fruits = Arrays.asList("Apple", "Mango", "Apple", "Banana", "Apple");
System.out.println("Apple count: " + Collections.frequency(fruits, "Apple"));
// fill — overwrite all elements
List<String> filled = new ArrayList<>(Arrays.asList("a", "b", "c"));
Collections.fill(filled, "X");
System.out.println("Filled: " + filled);
// unmodifiableList — read-only view
List<String> locked = Collections.unmodifiableList(fruits);
// locked.add("Grapes"); // UnsupportedOperationException
// nCopies — create list of n copies
List<String> copies = Collections.nCopies(4, "Java");
System.out.println("nCopies: " + copies);
// disjoint — true if no common elements
List<Integer> a = Arrays.asList(1, 2, 3);
List<Integer> b = Arrays.asList(4, 5, 6);
System.out.println("Disjoint: " + Collections.disjoint(a, b));
}
}Output
Sorted: [1, 2, 3, 5, 7, 8, 9] Reversed: [9, 8, 7, 5, 3, 2, 1] Shuffled: [3, 9, 1, 7, 2, 8, 5] Min: 1 Max: 9 Apple count: 3 Filled: [X, X, X] nCopies: [Java, Java, Java, Java] Disjoint: trueHow to Choose the Right Collection — Decision Flowchart
Choosing the right collection class is one of the most important skills in Java. This decision flowchart guides you to the right choice based on your requirements.
Code Execution Flow — from source to output
Real-World Code Examples
Example 1 — Student Grade Book using HashMap + TreeMap
import java.util.*;
public class GradeBook {
public static void main(String[] args) {
Map<String, List<Integer>> gradeBook = new HashMap<>();
// Add student marks (multiple subjects)
gradeBook.put("Alice", Arrays.asList(85, 92, 78, 95, 88));
gradeBook.put("Bob", Arrays.asList(72, 68, 75, 80, 70));
gradeBook.put("Charlie", Arrays.asList(95, 97, 92, 98, 94));
gradeBook.put("Diana", Arrays.asList(60, 65, 70, 55, 62));
// Calculate and display averages using TreeMap for sorted output
Map<String, Double> averages = new TreeMap<>();
for (Map.Entry<String, List<Integer>> entry : gradeBook.entrySet()) {
double avg = entry.getValue().stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
averages.put(entry.getKey(), avg);
}
System.out.println("=== Student Averages (Alphabetical) ===");
averages.forEach((name, avg) ->
System.out.printf("%-10s : %.1f%n", name, avg));
// Top scorer
String topScorer = Collections.max(averages.entrySet(),
Map.Entry.comparingByValue()).getKey();
System.out.println("\nTop Scorer: " + topScorer +
" (" + averages.get(topScorer) + ")");
}
}Output
=== Student Averages (Alphabetical) === Alice : 87.6 Bob : 73.0 Charlie : 95.2 Diana : 62.4 Top Scorer: Charlie (95.2)Example 2 — Word Frequency Counter using HashMap
import java.util.*;
public class WordFrequency {
public static void main(String[] args) {
String text = "java is great java is fast python is popular java is everywhere";
String[] words = text.split(" ");
// Count frequencies
Map<String, Integer> freq = new HashMap<>();
for (String word : words) {
freq.merge(word, 1, Integer::sum);
}
// Sort by frequency descending
List<Map.Entry<String, Integer>> sorted = new ArrayList<>(freq.entrySet());
sorted.sort(Map.Entry.<String, Integer>comparingByValue().reversed());
System.out.println("Word Frequencies:");
sorted.forEach(e -> System.out.printf(" %-10s : %d%n", e.getKey(), e.getValue()));
}
}Output
Word Frequencies: java : 4 is : 4 great : 1 fast : 1 python : 1 popular : 1 everywhere : 1Practice This Code — Live Editor
Advantages and Disadvantages of Java Collections Framework
The Java Collections Framework is a powerful tool, but like every design decision in software engineering, it has trade-offs worth understanding.
Java Collections — Interview Questions
These are the most frequently asked Java Collections interview questions for fresher to senior-level positions. Every Java developer must know these.
Practice Questions — Test Your Knowledge
Test your understanding of Java Collections with these practice questions. Cover from basic concept checks to tricky output questions and real-world design scenarios.
1. You need to store a list of student names and retrieve them in the order they were added. Which collection do you use and why?
Easy2. What is the output? Set<String> set = new TreeSet<>(); set.add("Banana"); set.add("Apple"); set.add("Mango"); set.add("Apple"); System.out.println(set);
Easy3. A HashMap has 1000 entries. What is the time complexity of get(key)? What is the worst case?
Medium4. What will this code print and why? Map<String, Integer> map = new HashMap<>(); map.put("A", 1); map.put("B", 2); map.put("A", 3); System.out.println(map.size() + " " + map.get("A"));
Easy5. You need to find all unique IP addresses from a log file (order doesn't matter) and count how many requests each IP made. Which collections do you use?
Medium6. What is wrong with this code? How do you fix it? List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5)); for (Integer n : list) { if (n % 2 == 0) list.remove(n); }
Medium7. Design an LRU Cache for 3 elements using Java Collections.
Hard8. What is the difference between List.of() and new ArrayList() in terms of mutability?
MediumConclusion — Mastering Java Collections
The Java Collections Framework is not just a utility library — it is the backbone of virtually every Java application ever written. Understanding when to reach for an ArrayList vs a LinkedList, a HashMap vs a TreeMap, or a HashSet vs a LinkedHashSet is what separates a junior developer from a senior engineer.
Your next steps: dive deep into Java Streams API (the functional way to process collections), explore Java Generics in depth (wildcards, bounded types), and study the java.util.concurrent package for thread-safe collection patterns used in enterprise systems.
Collections mastery is a career multiplier. Every data-intensive feature — search, filter, aggregate, sort, deduplicate — runs through Java Collections. The developer who chooses the right collection writes code that is faster, cleaner, and easier to maintain. Master them. ☕