Java ArrayList Class
A complete guide to the Java ArrayList class — dynamic resizing, generics, CRUD operations, sorting, iteration methods, ArrayList vs Array vs LinkedList vs Vector, nested ArrayList, and best practices with real-world examples.
Last Updated
March 2026
Read Time
18 min
Level
Beginner to Intermediate
What is the Java ArrayList Class?
The ArrayList class in Java is a part of the java.util package and provides a resizable (dynamic) array implementation of the List interface. It is the most widely used collection class in Java — the go-to choice whenever you need an ordered, index-accessible list of elements whose size is not known in advance.
Unlike a plain Java array — which has a fixed size set at creation — an ArrayList automatically grows its internal array when elements are added beyond current capacity, and can shrink it when needed. It preserves insertion order, allows duplicate elements, and permits null values. All elements are accessible by zero-based integer index in O(1) time.
Think of an ArrayList as a smart, elastic shelf. You can keep adding books (elements) to it — when it runs out of space, it quietly moves everything to a bigger shelf. You can reach any book instantly by its position number (index), and the shelf always knows exactly how many books it holds.
ArrayList Syntax and Constructors
The ArrayList class provides three constructors. Always use generics (ArrayList<Type>) to ensure type safety and avoid raw-type warnings.
import java.util.ArrayList;
import java.util.List;
// 1. Default constructor — internal capacity starts at 10
ArrayList<String> list1 = new ArrayList<>();
// 2. Constructor with initial capacity — avoids early resize
ArrayList<Integer> list2 = new ArrayList<>(50);
// 3. Constructor from existing Collection — copies all elements
List<String> source = List.of("Java", "Python", "Kotlin");
ArrayList<String> list3 = new ArrayList<>(source);
// Best practice: declare using List interface (program to interface)
List<String> languages = new ArrayList<>();import java.util.ArrayList;
import java.util.List;
public class ArrayListIntro {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
// Add elements
fruits.add("Mango");
fruits.add("Banana");
fruits.add("Papaya");
fruits.add("Guava");
fruits.add("Mango"); // duplicates allowed
fruits.add(null); // null allowed
System.out.println("List : " + fruits);
System.out.println("Size : " + fruits.size());
System.out.println("Index 1: " + fruits.get(1));
System.out.println("Has Banana? " + fruits.contains("Banana"));
}
}Output
List : [Mango, Banana, Papaya, Guava, Mango, null] Size : 6 Index 1: Banana Has Banana? trueWhy Use ArrayList?
ArrayList is the default choice for most Java list operations. Here is why it dominates everyday Java development:
- ▶
📐 Dynamic Size — No need to declare size upfront. ArrayList grows on demand. This eliminates ArrayIndexOutOfBoundsException from under-sized fixed arrays and memory waste from over-sized ones.
- ▶
⚡ Fast Random Access — Backed by an array, ArrayList provides O(1) get(index) and set(index) — as fast as a plain array. Perfect for workloads that read by index far more than they insert or delete.
- ▶
🔗 Full List Interface — ArrayList implements java.util.List completely, making it compatible with Collections utility methods (sort, shuffle, binarySearch, reverse, frequency, disjoint) and all Java APIs expecting a List.
- ▶
🔁 Multiple Iteration Styles — Supports for-each, traditional for loop, Iterator, ListIterator, and Java 8+ streams — giving flexibility to choose the best traversal approach for each situation.
- ▶
🧬 Generics Support — ArrayList<T> enforces type safety at compile time. You cannot accidentally add a String to an ArrayList<Integer>, catching bugs before runtime.
- ▶
🏗️ Rich API — Built-in methods for add, remove, search (contains, indexOf, lastIndexOf), bulk operations (addAll, removeAll, retainAll, containsAll), subList, and conversion to array.
- ▶
🌐 Framework Integration — Virtually every Java framework (Spring, Hibernate, Jackson, JAX-RS) works with List and returns or accepts ArrayList. It is the universal currency of Java data passing.
ArrayList vs Array vs LinkedList vs Vector
Choosing the right data structure depends on your access patterns, thread requirements, and whether size is known upfront. This table covers all four common options:
Decision guide: Use ArrayList by default for single-threaded ordered lists. Use LinkedList only if you frequently insert/delete at both ends. Use int[] arrays for primitive numeric data with fixed size. Avoid Vector in new code — use Collections.synchronizedList(new ArrayList<>()) instead.
ArrayList in the Java Collections Hierarchy
Understanding where ArrayList sits in Java's Collections hierarchy clarifies which interfaces it fulfills and which methods it inherits from each level.
RandomAccess marker interface: ArrayList implements RandomAccess — a marker interface signaling that index-based access is O(1). Algorithms in Collections (like binarySearch) check for this interface and use a faster index-based strategy when present, instead of iterating sequentially.
Important ArrayList Methods
ArrayList inherits from List and Collection and also has its own capacity-management methods. Here is the complete method reference:
CRUD Operations on ArrayList
ArrayList supports all four CRUD operations — Create, Read, Update, and Delete — with clean, expressive syntax. Here is a comprehensive demo of every major operation:
import java.util.*;
public class ArrayListCRUD {
public static void main(String[] args) {
List<String> students = new ArrayList<>();
// ---- CREATE ----
students.add("Arjun");
students.add("Priya");
students.add("Rohan");
students.add("Sneha");
students.add(1, "Vikram"); // insert at index 1
students.addAll(List.of("Aisha", "Dev")); // bulk add
System.out.println("After CREATE : " + students);
// ---- READ ----
System.out.println("get(0) : " + students.get(0));
System.out.println("size() : " + students.size());
System.out.println("contains Priya? " + students.contains("Priya"));
System.out.println("indexOf Rohan : " + students.indexOf("Rohan"));
System.out.println("subList(1,3) : " + students.subList(1, 3));
// ---- UPDATE ----
students.set(0, "Arjun Kumar"); // replace at index 0
System.out.println("After UPDATE : " + students);
// ---- DELETE ----
students.remove("Dev"); // remove by value
students.remove(2); // remove by index
students.removeIf(s -> s.startsWith("A")); // remove by condition
System.out.println("After DELETE : " + students);
// ---- BULK OPERATIONS ----
List<String> toRemove = List.of("Priya", "Sneha");
students.removeAll(toRemove);
System.out.println("After removeAll: " + students);
}
}Output
After CREATE : [Arjun, Vikram, Priya, Rohan, Sneha, Aisha, Dev] get(0) : Arjun size() : 7 contains Priya? true indexOf Rohan : 3 subList(1,3) : [Vikram, Priya] After UPDATE : [Arjun Kumar, Vikram, Priya, Rohan, Sneha, Aisha, Dev] After DELETE : [Vikram, Rohan, Sneha] After removeAll: [Vikram]Sorting and Searching an ArrayList
ArrayList integrates seamlessly with Collections utility methods and Java 8+ lambda-based sorting. You can sort in natural order, reverse order, or by any custom comparator.
import java.util.*;
public class SortSearchDemo {
public static void main(String[] args) {
List<Integer> scores = new ArrayList<>(Arrays.asList(78, 45, 92, 61, 88, 34, 99, 55));
System.out.println("Original : " + scores);
// Natural (ascending) sort
Collections.sort(scores);
System.out.println("Ascending: " + scores);
// Reverse (descending) sort
scores.sort(Collections.reverseOrder());
System.out.println("Descending: " + scores);
// Binary search (list must be sorted ascending first)
Collections.sort(scores);
int idx = Collections.binarySearch(scores, 88);
System.out.println("binarySearch(88) at index: " + idx);
// --- Sorting custom objects with Comparator ---
List<String> names = new ArrayList<>(Arrays.asList("Zara", "Amit", "Riya", "Kabir"));
// Sort by length, then alphabetically
names.sort(Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
System.out.println("Sorted by length: " + names);
// Reverse alphabetical
names.sort(Comparator.reverseOrder());
System.out.println("Reverse alpha: " + names);
// Min and Max
System.out.println("Min: " + Collections.min(scores));
System.out.println("Max: " + Collections.max(scores));
}
}Output
Original : [78, 45, 92, 61, 88, 34, 99, 55] Ascending: [34, 45, 55, 61, 78, 88, 92, 99] Descending: [99, 92, 88, 78, 61, 55, 45, 34] binarySearch(88) at index: 6 Sorted by length: [Zara, Amit, Riya, Kabir] Reverse alpha: [Zara, Riya, Kabir, Amit] Min: 34 Max: 99Iterating an ArrayList — 6 Ways
ArrayList supports six distinct iteration techniques. Each has specific strengths — choosing the right one improves both performance and code clarity.
import java.util.*;
import java.util.stream.Collectors;
public class IterationDemo {
public static void main(String[] args) {
List<String> cities = new ArrayList<>(List.of("Delhi", "Mumbai", "Pune", "Jaipur", "Surat"));
// 1. Traditional for loop (index-based) — best when index needed
System.out.println("1. for loop:");
for (int i = 0; i < cities.size(); i++) {
System.out.print(cities.get(i) + " ");
}
// 2. Enhanced for-each — cleanest for read-only traversal
System.out.println("\n2. for-each:");
for (String city : cities) {
System.out.print(city + " ");
}
// 3. Iterator — use when removing elements during traversal
System.out.println("\n3. Iterator (removes 'Pune'):");
Iterator<String> it = cities.iterator();
while (it.hasNext()) {
String c = it.next();
if (c.equals("Pune")) it.remove(); // safe removal
else System.out.print(c + " ");
}
// 4. ListIterator — bidirectional, supports add/set during traversal
System.out.println("\n4. ListIterator (reverse):");
ListIterator<String> li = cities.listIterator(cities.size());
while (li.hasPrevious()) {
System.out.print(li.previous() + " ");
}
// 5. forEach with lambda (Java 8+) — concise, functional style
System.out.println("\n5. forEach lambda:");
cities.forEach(city -> System.out.print(city + " "));
// 6. Stream API (Java 8+) — for filter/map/collect pipelines
System.out.println("\n6. Stream (filter cities with length > 4):");
List<String> filtered = cities.stream()
.filter(c -> c.length() > 4)
.sorted()
.collect(Collectors.toList());
System.out.println(filtered);
}
}Output
1. for loop: Delhi Mumbai Pune Jaipur Surat 2. for-each: Delhi Mumbai Pune Jaipur Surat 3. Iterator (removes 'Pune'): Delhi Mumbai Jaipur Surat 4. ListIterator (reverse): Surat Jaipur Mumbai Delhi 5. forEach lambda: Delhi Mumbai Jaipur Surat 6. Stream (filter cities with length > 4): [Delhi, Jaipur, Mumbai, Surat]ArrayList Internal Resize Flow — Flowchart
The flowchart below shows exactly what happens inside ArrayList when you call add(element) — how it decides whether to grow and how it calculates the new capacity.
Code Execution Flow — from source to output
Growth formula: newCapacity = oldCapacity + (oldCapacity >> 1) — this is an arithmetic right shift by 1, equivalent to adding 50% of the current capacity. So a capacity-10 ArrayList grows to 15, then 22, then 33, then 49, and so on. This amortizes the O(n) copy cost across many insertions, giving add() an amortized O(1) time complexity.
ArrayList with Generics and Custom Objects
One of ArrayList's most powerful capabilities is working with custom objects via generics. You can store, sort, filter, and manipulate any user-defined class in a type-safe ArrayList.
import java.util.*;
import java.util.stream.Collectors;
class Student {
String name;
int marks;
Student(String name, int marks) {
this.name = name;
this.marks = marks;
}
@Override
public String toString() {
return name + "(" + marks + ")";
}
}
public class CustomObjectList {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Arjun", 88));
students.add(new Student("Priya", 95));
students.add(new Student("Rohan", 72));
students.add(new Student("Sneha", 89));
students.add(new Student("Vikram", 65));
System.out.println("All students: " + students);
// Sort by marks descending
students.sort(Comparator.comparingInt((Student s) -> s.marks).reversed());
System.out.println("By marks (desc): " + students);
// Sort by name alphabetically
students.sort(Comparator.comparing(s -> s.name));
System.out.println("By name (alpha): " + students);
// Filter students who scored >= 85 using Stream
List<Student> toppers = students.stream()
.filter(s -> s.marks >= 85)
.collect(Collectors.toList());
System.out.println("Toppers (>=85): " + toppers);
// Find the highest scorer
Student best = students.stream()
.max(Comparator.comparingInt(s -> s.marks))
.orElseThrow();
System.out.println("Top scorer: " + best);
}
}Output
All students: [Arjun(88), Priya(95), Rohan(72), Sneha(89), Vikram(65)] By marks (desc): [Priya(95), Sneha(89), Arjun(88), Rohan(72), Vikram(65)] By name (alpha): [Arjun(88), Priya(95), Rohan(72), Sneha(89), Vikram(65)] Toppers (>=85): [Arjun(88), Priya(95), Sneha(89)] Top scorer: Priya(95)Nested ArrayList — 2D List in Java
Java does not have a built-in 2D ArrayList, but you can easily create one using ArrayList<ArrayList<T>>. This is commonly used for matrix operations, adjacency lists in graphs, and storing variable-length rows.
import java.util.*;
public class NestedArrayList {
public static void main(String[] args) {
// Create a 3x3 matrix using nested ArrayList
List<List<Integer>> matrix = new ArrayList<>();
for (int i = 0; i < 3; i++) {
List<Integer> row = new ArrayList<>();
for (int j = 0; j < 3; j++) {
row.add((i + 1) * (j + 1)); // multiplication table
}
matrix.add(row);
}
// Print the matrix
System.out.println("Multiplication Matrix:");
for (List<Integer> row : matrix) {
for (int val : row) {
System.out.printf("%4d", val);
}
System.out.println();
}
// Access specific element — row 1, col 2 (0-indexed)
System.out.println("\nElement at [1][2]: " + matrix.get(1).get(2));
// Variable-length rows (jagged 2D list)
List<List<String>> jagged = new ArrayList<>();
jagged.add(new ArrayList<>(List.of("A")));
jagged.add(new ArrayList<>(List.of("B", "C")));
jagged.add(new ArrayList<>(List.of("D", "E", "F")));
System.out.println("\nJagged List: " + jagged);
}
}Output
Multiplication Matrix: 1 2 3 2 4 6 3 6 9 Element at [1][2]: 6 Jagged List: [[A], [B, C], [D, E, F]]Best Practices for Using ArrayList
Writing clean, efficient ArrayList code requires following these industry-standard best practices used in professional Java development:
- ▶
✅ 1. Program to the List Interface — Always declare ArrayList variables as List<T> not ArrayList<T>. For example: List<String> names = new ArrayList<>(). This decouples your code from the specific implementation, making it easy to swap to LinkedList or CopyOnWriteArrayList without changing callers.
- ▶
✅ 2. Specify Initial Capacity When Size Is Known — If you expect approximately N elements, construct with new ArrayList<>(N). This prevents multiple internal resize-and-copy cycles, improving performance for large lists. This is especially important in loops that add thousands of elements.
- ▶
✅ 3. Always Use Generics — Never Raw Types — List list = new ArrayList() is a raw type and bypasses compile-time type checking. Always use List<String> list = new ArrayList<>() to catch type mismatch bugs at compile time, not at runtime.
- ▶
✅ 4. Use Iterator or removeIf() for Deletion During Traversal — Never remove elements inside a for-each loop — it throws ConcurrentModificationException. Use Iterator.remove() for conditional removal during traversal, or the cleaner Java 8+ list.removeIf(predicate) for batch removal.
- ▶
✅ 5. Use Collections.unmodifiableList() for Read-Only Lists — When passing a list to external code that should not modify it, wrap it: Collections.unmodifiableList(myList). Any modification attempt throws UnsupportedOperationException immediately, preventing accidental mutation.
- ▶
✅ 6. Prefer List.of() for Fixed Immutable Lists — For a list of known constants that will never change, Java 9+ List.of("A", "B", "C") creates a compact, immutable list faster than constructing an ArrayList. Never use List.of() when you need to add or remove elements later.
- ▶
✅ 7. Use Stream API for Filter/Map/Collect Pipelines — For transformations (filtering, mapping, grouping), streams are more readable and often more performant than manual loops over ArrayList. Prefer list.stream().filter().map().collect() over imperative for-loops for such operations.
- ▶
❌ 8. Avoid Frequent Insertions at Index 0 — Inserting at the beginning of an ArrayList is O(n) because all existing elements must shift right. For frequent head insertions or removals, use LinkedList or ArrayDeque instead.
import java.util.*;
import java.util.stream.Collectors;
public class BestPracticesDemo {
public static void main(String[] args) {
// ✅ Program to interface
List<String> names = new ArrayList<>(10); // initial capacity hint
names.addAll(List.of("Alice", "Bob", "Charlie", "David", "Eve"));
// ✅ removeIf — cleaner than iterator loop
names.removeIf(name -> name.startsWith("D"));
System.out.println("After removeIf: " + names);
// ✅ Stream pipeline — filter and collect
List<String> longNames = names.stream()
.filter(n -> n.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Long names uppercase: " + longNames);
// ✅ Immutable view — safe to pass externally
List<String> readOnly = Collections.unmodifiableList(names);
System.out.println("Read-only view: " + readOnly);
// readOnly.add("Frank"); // ← UnsupportedOperationException
// ✅ List.of() for known constants
List<String> weekdays = List.of("Mon", "Tue", "Wed", "Thu", "Fri");
System.out.println("Weekdays: " + weekdays);
}
}Output
After removeIf: [Alice, Bob, Charlie, Eve] Long names uppercase: [ALICE, CHARLIE] Read-only view: [Alice, Bob, Charlie, Eve] Weekdays: [Mon, Tue, Wed, Thu, Fri]Real-World Code Examples
Example 1 — Shopping Cart Manager
import java.util.*;
import java.util.stream.Collectors;
class Product {
String name;
double price;
int quantity;
Product(String name, double price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
double total() { return price * quantity; }
@Override
public String toString() {
return String.format("%s x%d @₹%.0f", name, quantity, price);
}
}
public class ShoppingCart {
private List<Product> cart = new ArrayList<>();
public void addItem(Product p) { cart.add(p); }
public void removeItem(String name) {
cart.removeIf(p -> p.name.equalsIgnoreCase(name));
}
public double getTotal() {
return cart.stream().mapToDouble(Product::total).sum();
}
public void printCart() {
System.out.println("--- Cart ---");
cart.forEach(p -> System.out.println(" " + p + " = ₹" + p.total()));
System.out.printf(" Total: ₹%.2f%n", getTotal());
}
public static void main(String[] args) {
ShoppingCart sc = new ShoppingCart();
sc.addItem(new Product("Rice", 55.0, 2));
sc.addItem(new Product("Dal", 120.0, 1));
sc.addItem(new Product("Oil", 180.0, 1));
sc.addItem(new Product("Biscuit", 30.0, 3));
sc.printCart();
sc.removeItem("Dal");
System.out.println("\nAfter removing Dal:");
sc.printCart();
}
}Output
--- Cart --- Rice x2 @₹55 = 110.0 Dal x1 @₹120 = 120.0 Oil x1 @₹180 = 180.0 Biscuit x3 @₹30 = 90.0 Total: ₹500.00 After removing Dal: --- Cart --- Rice x2 @₹55 = 110.0 Oil x1 @₹180 = 180.0 Biscuit x3 @₹30 = 90.0 Total: ₹380.00Example 2 — Word Frequency Counter
import java.util.*;
import java.util.stream.Collectors;
public class WordFrequency {
public static void main(String[] args) {
String text = "java is great java is fast python is popular java rocks";
List<String> words = new ArrayList<>(Arrays.asList(text.split(" ")));
System.out.println("Total words: " + words.size());
System.out.println("Unique words: " + new HashSet<>(words).size());
// Count frequency using stream groupingBy
Map<String, Long> freq = words.stream()
.collect(Collectors.groupingBy(w -> w, Collectors.counting()));
// Sort by frequency descending
freq.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.forEach(e -> System.out.println(" " + e.getKey() + " : " + e.getValue()));
// Get distinct words sorted alphabetically
List<String> distinct = words.stream()
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println("Distinct (sorted): " + distinct);
}
}Output
Total words: 10 Unique words: 5 java : 3 is : 3 great : 1 fast : 1 python : 1 popular : 1 rocks : 1 Distinct (sorted): [fast, great, is, java, popular, python, rocks]Practice This Code — Live Editor
Advantages and Disadvantages of ArrayList
ArrayList is the most versatile and widely used collection in Java — but it is not the right tool for every job. Understanding its trade-offs helps you choose correctly.
Java ArrayList — Interview Questions
These are the most frequently asked interview questions on Java ArrayList for Java developer, backend engineer, and SDET positions.
Practice Questions — Test Your Knowledge
Test your understanding of ArrayList with these practice questions. Attempt each before revealing the answer.
1. What is the capacity of an ArrayList after adding 16 elements to a default-constructed ArrayList?
Easy2. What is the output? List<String> list = new ArrayList<>(List.of("A","B","C")); list.remove(1); System.out.println(list);
Easy3. Why does this code throw ConcurrentModificationException and how do you fix it? for (String s : list) { if (s.equals("X")) list.remove(s); }
Medium4. What is the difference in behavior: list.remove(2) vs list.remove(Integer.valueOf(2)) on an ArrayList<Integer>?
Medium5. You have an ArrayList of 1 million elements. Describe the performance of: (a) get(500000), (b) add("X") at end, (c) add(0, "X") at beginning, (d) contains("X") — explain Big-O for each.
Hard6. What is the output? List<Integer> a = new ArrayList<>(List.of(1,2,3,4,5)); List<Integer> b = a.subList(1,4); b.set(0, 99); System.out.println(a);
Hard7. What happens when you call Collections.sort() on an ArrayList of custom objects that do not implement Comparable?
Medium8. Can an ArrayList hold different types of objects simultaneously? Is it good practice?
EasyConclusion — Mastering Java ArrayList
ArrayList is the workhorse of Java development — the first data structure you reach for when you need an ordered, indexed, dynamically-sized collection. Its combination of O(1) random access, rich API, seamless generics support, and full Collections Framework integration makes it the correct default choice for the vast majority of Java list use cases.
Mastering ArrayList means knowing not just how to use it, but when not to: use LinkedList when you frequently insert at both ends, use CopyOnWriteArrayList when reads dominate in a multi-threaded context, use int[] when you need a large primitive array without boxing overhead, and use List.of() when immutability is a requirement.
Your next steps: explore LinkedList for pointer-based list operations, dive into the Stream API for functional list processing, study Java 21's Sequenced Collections interface that standardizes first/last element access, and learn Collections utility methods (sort, binarySearch, nCopies, frequency, disjoint) that supercharge your ArrayList operations.
Remember: Declare as List, instantiate as ArrayList, specify initial capacity when size is known, never modify during for-each, and always use generics. These five habits will keep your ArrayList code correct, performant, and production-ready. ☕