ArrayList Tutorial

Java ArrayList Tutorial: Every Method with Examples (2026) | D.Tech Academic

Java ArrayList Tutorial: Every Method with Examples (2026)

Last Updated: | Author: Deep Singh

ArrayList is the workhorse of the Java Collections Framework. Need a list that grows as you add items? Use an ArrayList. Need random access by index? ArrayList. Need to sort, filter, search, or stream over a collection? ArrayList. It's the class you'll reach for 90% of the time when you need a list in Java.

In this tutorial, you'll get a walkthrough of every important ArrayList method — add, remove, get, set, iterate, sublist, stream operations, and more. Each one has a runnable example. By the end, you'll have a complete mental map of what ArrayList can do.

Video Walkthrough

If you prefer learning by watching, here's a video walkthrough that covers the same material:

Creating an ArrayList

Use a generic type parameter — like <String> — to tell the compiler what kind of items the list holds. This gives you type safety: no casts, no ClassCastException at runtime.

ArrayList<String> list = new ArrayList<>();

The empty diamond <> on the right side is the "diamond operator" — Java infers the type from the left side. This is the modern style since Java 7.

Adding Items

Append to the End

boolean first = list.add("suresh");   // returns true if added
boolean second = list.add("ashok");

The return value is always true for ArrayList, but the method signature returns boolean for compatibility with other collections like Set, which can reject duplicates.

Insert at a Specific Position

list.add(1, "harry");   // inserts at index 1, shifts others to the right

If the index is larger than the size, this throws IndexOutOfBoundsException. Note that list.add(list.size(), item) is a valid way to append, but plain list.add(item) is the idiomatic choice.

Iterating Over Items

Iterator (Basic)

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    System.out.println(item);
}

Because the collection is generic, iterator.next() returns a String directly — no casting needed. That's the whole point of generics.

ListIterator (Bidirectional)

ListIterator<String> listIterator = list.listIterator();

// Forward
while (listIterator.hasNext()) {
    System.out.println(listIterator.next());
}

// Backward (must have iterated to the end first)
while (listIterator.hasPrevious()) {
    System.out.println(listIterator.previous());
}

ListIterator is a richer version of Iterator that also lets you move backward and replace or insert elements during iteration.

For-Each Loop (Simplest)

for (String item : list) {
    System.out.println(item);
}

The for-each loop is what you'll use 95% of the time. It's the cleanest way to iterate.

Lambda forEach (Java 8+)

list.forEach(item -> System.out.println(item));

This is the functional style, introduced in Java 8. It's elegant but a bit slower than a plain for-each loop due to the extra lambda indirection. For most code, the difference doesn't matter.

Removing Items

Remove by Index

String removed = list.remove(0);   // removes and returns the item at index 0

Remove by Value

boolean status = list.remove("harry");   // returns true if the item was found and removed

Watch out for the overload trap: list.remove(2) removes by index (the item at position 2), while list.remove(Integer.valueOf(2)) removes by value (the item equal to 2). This is a classic source of bugs when working with ArrayList<Integer>.

Remove All Items

list.clear();

Accessing Items

Get by Index

String item = list.get(0);   // returns the item at index 0

Get a Sublist

List<String> sub = list.subList(0, 2);   // from index 0 (inclusive) to 2 (exclusive)

Important: subList returns a view of the original list, not a copy. Modifying the sublist modifies the original, and vice versa. If you want an independent copy, use new ArrayList<>(list.subList(0, 2)).

Replace by Index

String replaced = list.set(0, "ambuj");   // replaces item at index 0, returns the old item

Size

int size = list.size();

Check if Empty

boolean empty = list.isEmpty();

Use isEmpty() instead of size() == 0 — it's cleaner and slightly more efficient.

Searching and Checking

Check if an Item Exists

boolean exists = list.contains("suresh");

Find First Occurrence

int index = list.indexOf("suresh");   // returns -1 if not found

Find Last Occurrence

int lastIndex = list.lastIndexOf("suresh");

Both methods return -1 when the item isn't present. Always check for that before using the result as an index.

Converting and Combining

Convert to Array

Object[] items = list.toArray();

For a typed array, pass a zero-length array of the right type:

String[] items = list.toArray(new String[0]);

The new String[0] is a small idiom — the JVM uses it to know the target type and (in modern JVMs) it's actually faster than allocating the right size upfront.

Add All Items from Another Collection

ArrayList<String> another = new ArrayList<>();
another.add("nitish");
another.add("punit");

boolean changed = list.addAll(another);

Add All Items at a Specific Position

list.addAll(0, another);   // inserts all items from "another" starting at index 0

Remove All Items in Another Collection

boolean changed = list.removeAll(another);

Keep Only Items That Are in Another Collection

boolean changed = list.retainAll(another);

This is the set-intersection operation. After this call, list contains only the items that were also in another.

Streams and Lambda Expressions

Java 8 introduced the Stream API, which lets you process collections with a functional style. The main methods are filter, map, limit, collect, and count.

Iterate with forEach

list.forEach(item -> System.out.println(item));

Filter and Find Any Match

Optional<String> data = list.stream()
    .filter(x -> x.equals("suresh"))
    .findAny();

if (data.isPresent()) {
    System.out.println("exists");
} else {
    System.out.println("not exists");
}

Filter, Limit, and Count

long count = list.stream()
    .filter(x -> x.length() > 3)
    .limit(2)
    .count();

System.out.println(count);

Collect Filtered Items into a New List

List<String> filtered = list.stream()
    .filter(x -> x.length() > 0)
    .collect(Collectors.toList());

filtered.forEach(x -> System.out.println(x));

Parallel Stream

List<String> matches = list.parallelStream()
    .filter(x -> x.contains("suresh"))
    .collect(Collectors.toList());

System.out.println(matches.size());

parallelStream() splits the work across multiple CPU cores. It's faster for large collections with expensive operations — but for small lists (anything under a few thousand items), the overhead of splitting and merging can make it slower than a sequential stream. Measure before optimising.

Full Runnable Example

Here's the complete Test.java covering every method in this tutorial. You can copy it into any Java project and run it directly.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Optional;
import java.util.stream.Collectors;

public class Test {

    public static void main(String[] args) {

        // --- Create ---
        ArrayList<String> list = new ArrayList<>();

        // --- Add ---
        list.add("suresh");
        list.add("ashok");
        list.add(1, "harry");

        // --- Iterate with Iterator ---
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            System.out.println(item);
        }

        // --- Remove by index and by value ---
        String removed = list.remove(0);
        boolean wasRemoved = list.remove("harry");

        // --- Access ---
        String firstItem = list.get(0);
        String replaced = list.set(0, "ambuj");
        List<String> subItems = list.subList(0, Math.min(2, list.size()));

        // --- Search ---
        int index = list.indexOf("suresh");
        int lastIndex = list.lastIndexOf("suresh");
        boolean exists = list.contains("suresh");

        // --- Size and emptiness ---
        int size = list.size();
        boolean empty = list.isEmpty();

        // --- Convert to array ---
        String[] items = list.toArray(new String[0]);

        // --- Combine with another collection ---
        ArrayList<String> another = new ArrayList<>();
        another.add("nitish");
        another.add("punit");

        list.addAll(another);
        list.addAll(0, another);
        list.removeAll(another);
        list.retainAll(another);

        // --- Lambda iteration ---
        list.forEach(x -> System.out.println(x));

        // --- Filter with stream ---
        Optional<String> data = list.stream()
            .filter(x -> x.equals("suresh"))
            .findAny();

        if (data.isPresent()) {
            System.out.println("exists");
        } else {
            System.out.println("not exists");
        }

        // --- Filter, limit, count ---
        long count = list.stream()
            .filter(x -> x.length() > 3)
            .limit(2)
            .count();
        System.out.println(count);

        // --- Collect filtered into a new list ---
        List<String> filtered = list.stream()
            .filter(x -> x.length() > 0)
            .collect(Collectors.toList());
        filtered.forEach(x -> System.out.println(x));

        // --- Parallel stream ---
        List<String> matches = list.parallelStream()
            .filter(x -> x.contains("suresh"))
            .collect(Collectors.toList());
        System.out.println(matches.size());

        // --- Clear ---
        list.clear();
        System.out.println("Final size: " + list.size());
    }
}

A word on this code: The original tutorial mixed every method into one file without checking intermediate results. If you actually run the original code, it throws IndexOutOfBoundsException because the list is emptied by clear() before later operations that need items. The version above keeps the operations ordered so the list is never empty when a method needs items. This is the kind of detail that only shows up when you actually run the code.

Common Pitfalls

  • remove(int) vs remove(Object). list.remove(2) removes by index. list.remove(Integer.valueOf(2)) removes by value. This trips up almost everyone working with ArrayList<Integer>.
  • Assuming subList returns a copy. It returns a view. Modifying either the sublist or the original list affects the other. Use new ArrayList<>(list.subList(...)) for an independent copy.
  • Modifying the list while iterating. Calling list.add() or list.remove() inside an Iterator loop throws ConcurrentModificationException. Use the iterator's own remove() method, or collect changes and apply them after the loop.
  • Using size() == 0 instead of isEmpty(). Both work, but isEmpty() is clearer and communicates intent better.
  • Not checking indexOf() for -1. If the item isn't found, indexOf returns -1. Using that as an index to get() or remove() throws IndexOutOfBoundsException.
  • Using parallelStream() for small lists. The overhead of splitting the work usually makes it slower for anything under a few thousand elements. Use stream() by default.
  • Assuming ArrayList is thread-safe. It isn't. If multiple threads will access the list, use Collections.synchronizedList() or a concurrent collection like CopyOnWriteArrayList.
  • Forgetting to specify the generic type. Raw ArrayList list = new ArrayList(); compiles with warnings and requires explicit casts on every get(). Always use ArrayList<T>.
  • Not pre-sizing for large lists. If you know you'll add 100,000 items, pass the capacity upfront: new ArrayList<>(100000). This avoids repeated array reallocations as the list grows.
  • Confusing ArrayList with LinkedList. ArrayList is fast for random access by index but slow for insertion at the front. LinkedList is the opposite. Choose based on your access pattern.

Wrapping Up

You now have a complete reference for Java's ArrayList — from creating and adding, through iterating and removing, to streams and parallel processing. The methods you'll actually use day to day are add, get, remove, size, contains, and forEach. The rest are there when you need them.

From here, you can explore:

  • LinkedList — the alternative list implementation, better for frequent insertions at the front.
  • HashMap and HashSet — the other two collections you'll use constantly.
  • The Collections utility class — for sorting, reversing, shuffling, and binary search.
  • Stream API deeper dive — map, reduce, groupingBy, and other powerful operations.
  • Immutability with List.of() — the modern way to create small fixed lists (Java 9+).

No comments: