00:00

List Interface in Java – Complete Guide with Examples

The List interface represents an ordered collection in which every element has a position (an index), duplicates are allowed, and you can insert, read, replace and remove elements at any position. It lives in java.util, extends the Collection interface, and has been part of the Collections Framework since JDK 1.2.

You cannot create a List directly because it is an interface. Instead you choose an implementation such as ArrayList or LinkedList, and write your code against the List type. This tutorial explains the methods every list has, the main implementations, the different ways to create lists, and the mistakes to avoid.

1. What Is the List Interface?

Think of a numbered to-do list. Items stay in the order you wrote them, you can refer to "item 3", the same item can appear twice, and you can insert a new item between two others. Java's List describes exactly these abilities, without saying how they are stored.

  • Ordered: elements keep their insertion order.
  • Index-based: positions start at 0, and you can access, insert, replace and remove by index.
  • Duplicates allowed: the same value can appear many times.
  • null allowed by most implementations (but not by List.of()).
  • Generic: List<String> accepts only strings, so mistakes are caught when you compile.
  • ListIterator: a special iterator that can move both ways and change the list while walking it.

2. Hierarchy and Declaration

List extends Collection, which extends Iterable. Since Java 21 it also extends SequencedCollection, which adds methods for the first and last elements.

List interface hierarchy in Java
java.util.List
public interface List<E> extends SequencedCollection<E>   // Java 21+
// before Java 21: public interface List<E> extends Collection<E>

3. Using a List: The Basics

Declare the variable as List and create a concrete class on the right-hand side. All the methods you call come from the interface.

ListBasics.java
import java.util.ArrayList;
import java.util.List;

public class ListBasics {
    public static void main(String[] args) {
        List<String> books = new ArrayList<>();   // List reference, ArrayList object

        books.add("Clean Code");
        books.add("Effective Java");
        books.add(0, "Head First Java");          // positional insert
        System.out.println("Books:      " + books);

        System.out.println("get(1):     " + books.get(1));
        books.set(1, "Clean Architecture");
        System.out.println("After set:  " + books);

        books.remove("Effective Java");
        System.out.println("After remove: " + books + ", size=" + books.size());
    }
}
Output
Books:      [Head First Java, Clean Code, Effective Java]
get(1):     Clean Code
After set:  [Head First Java, Clean Architecture, Effective Java]
After remove: [Head First Java, Clean Architecture], size=2

4. List Implementations

ClassStored asBest for
ArrayListResizable arrayThe default choice: fast reading by index and fast adding at the end.
LinkedListDoubly linked nodesAdding and removing at both ends; also a queue or deque.
VectorResizable array, synchronizedLegacy code only.
StackExtends VectorLegacy code only; use ArrayDeque for stacks.
CopyOnWriteArrayListArray copied on every writeLists shared by threads that are read far more often than changed.

Because they all implement List, a method that accepts a List works with every one of them:

ListImplementations.java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;

public class ListImplementations {

    // Works with ANY List: the method depends only on the interface
    static void addSampleData(List<Integer> list) {
        list.add(30);
        list.add(10);
        list.add(20);
        list.sort(null);              // natural order
    }

    public static void main(String[] args) {
        List<List<Integer>> all = List.of(
                new ArrayList<>(), new LinkedList<>(), new Vector<>(), new CopyOnWriteArrayList<>());

        for (List<Integer> list : all) {
            addSampleData(list);
            System.out.printf("%-20s %s%n", list.getClass().getSimpleName(), list);
        }
    }
}
Output
ArrayList            [10, 20, 30]
LinkedList           [10, 20, 30]
Vector               [10, 20, 30]
CopyOnWriteArrayList [10, 20, 30]

5. Ways to Create a List

Java offers several ways to create a list, and they differ in one important way: whether the list can change afterwards.

CodeAdd or removeChange elements (set)Allows null
new ArrayList<>(...)YesYesYes
Arrays.asList(...)No (fixed size)Yes, and it writes to the arrayYes
List.of(...) (Java 9+)NoNoNo
List.copyOf(c) (Java 10+)NoNoNo
Collections.unmodifiableList(list)NoNoYes (read-only view of another list)
stream.toList() (Java 16+)NoNoYes
ListFactories.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class ListFactories {
    static void tryAdd(String label, List<String> list) {
        try {
            list.add("X");
            System.out.println(label + " add() worked: " + list);
        } catch (UnsupportedOperationException e) {
            System.out.println(label + " add() failed: UnsupportedOperationException");
        }
    }

    public static void main(String[] args) {
        String[] array = {"A", "B"};

        tryAdd("new ArrayList<>(...)        ", new ArrayList<>(List.of("A", "B")));
        tryAdd("Arrays.asList(...)          ", Arrays.asList(array));
        tryAdd("List.of(...)                ", List.of("A", "B"));
        tryAdd("Collections.unmodifiableList", Collections.unmodifiableList(new ArrayList<>()));

        // Arrays.asList() is a view of the array: set() writes through
        List<String> view = Arrays.asList(array);
        view.set(0, "Z");
        System.out.println("Array after view.set(0, Z): " + Arrays.toString(array));
    }
}
Output
new ArrayList<>(...)         add() worked: [A, B, X]
Arrays.asList(...)           add() failed: UnsupportedOperationException
List.of(...)                 add() failed: UnsupportedOperationException
Collections.unmodifiableList add() failed: UnsupportedOperationException
Array after view.set(0, Z): [Z, B]
Rule of thumb: use List.of() for fixed data such as constants, and new ArrayList<>(List.of(...)) when you need a list you can change.

6. Methods of the List Interface

On top of the methods inherited from Collection (such as size(), isEmpty(), contains() and clear()), List adds index-based methods:

MethodDescription
add(E e)Adds an element at the end.
add(int index, E e)Inserts an element at a position; later elements shift right.
addAll(int index, Collection c)Inserts all elements of a collection at a position.
get(int index)Returns the element at a position.
set(int index, E e)Replaces an element and returns the old one.
remove(int index)Removes the element at a position and returns it.
remove(Object o)Removes the first matching element.
indexOf(Object o), lastIndexOf(Object o)Position of the first or last match, or -1.
subList(int from, int to)A view of part of the list (from included, to excluded).
listIterator(), listIterator(int index)An iterator that can move both ways and modify the list.
sort(Comparator c)Sorts the list (null means natural order).
replaceAll(UnaryOperator op)Replaces every element with the result of a function.
of(...), copyOf(c)Static factory methods for unmodifiable lists.
addFirst, addLast, getFirst, getLast, removeFirst, removeLast, reversedSequenced methods added in Java 21.

7. Searching and Working with subList()

subList() does not copy anything. It returns a view backed by the original list, so changes to the view, such as clear(), change the original list too. This makes it an easy way to remove a range of elements.

ListSearchAndView.java
import java.util.ArrayList;
import java.util.List;

public class ListSearchAndView {
    public static void main(String[] args) {
        List<Integer> marks = new ArrayList<>(List.of(72, 85, 60, 85, 91, 47));

        System.out.println("indexOf(85):     " + marks.indexOf(85));
        System.out.println("lastIndexOf(85): " + marks.lastIndexOf(85));
        System.out.println("contains(100):   " + marks.contains(100));

        // subList() is a VIEW: changes go through to the original list
        List<Integer> middle = marks.subList(1, 4);
        System.out.println("subList(1, 4):   " + middle);
        middle.clear();                          // removes indexes 1..3 from marks
        System.out.println("After clear():   " + marks);

        marks.sort(null);
        System.out.println("Sorted:          " + marks);
    }
}
Output
indexOf(85):     1
lastIndexOf(85): 3
contains(100):   false
subList(1, 4):   [85, 60, 85]
After clear():   [72, 91, 47]
Sorted:          [47, 72, 91]
Careful: if you change the original list structurally after creating a sub-list (for example with add()), using the sub-list afterwards throws ConcurrentModificationException. Wrap it in new ArrayList<>(list.subList(a, b)) when you need an independent copy.

8. ListIterator

A normal Iterator only moves forward and can only remove. A ListIterator can also move backward, report the current index, replace the current element with set(), and insert with add().

ListIteratorDemo.java
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class ListIteratorDemo {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>(List.of("one", "two", "three"));

        ListIterator<String> it = words.listIterator();
        while (it.hasNext()) {
            int index = it.nextIndex();
            String w = it.next();
            System.out.println(index + " -> " + w);
            if (w.equals("two")) {
                it.set("TWO");          // replace the current element
                it.add("two-and-half"); // insert after it
            }
        }
        System.out.println("After forward pass: " + words);

        System.out.print("Backward: ");
        while (it.hasPrevious()) {
            System.out.print(it.previous() + " ");
        }
        System.out.println();
    }
}
Output
0 -> one
1 -> two
3 -> three
After forward pass: [one, TWO, two-and-half, three]
Backward: three two-and-half TWO one

Notice that index 2 was skipped in the output: add() inserted two-and-half at index 2, behind the cursor, so the next call to next() returned three at index 3.

9. Bulk Operations

Methods inherited from Collection let you work with whole lists at once: retainAll() keeps the common elements, removeAll() removes another list's elements, and addAll() appends them.

ListBulkOperations.java
import java.util.ArrayList;
import java.util.List;

public class ListBulkOperations {
    public static void main(String[] args) {
        List<String> teamA = new ArrayList<>(List.of("Java", "SQL", "Docker", "Git"));
        List<String> teamB = List.of("Git", "Java", "Kafka");

        List<String> common = new ArrayList<>(teamA);
        common.retainAll(teamB);                     // intersection
        System.out.println("Common skills:  " + common);

        List<String> onlyA = new ArrayList<>(teamA);
        onlyA.removeAll(teamB);                      // difference
        System.out.println("Only team A:    " + onlyA);

        List<String> all = new ArrayList<>(teamA);
        all.addAll(teamB);                           // union (with duplicates)
        all.replaceAll(String::toLowerCase);
        all.removeIf(s -> s.length() > 4);
        System.out.println("Short, lower:   " + all);

        System.out.println("Equal lists?    " + List.of(1, 2).equals(new ArrayList<>(List.of(1, 2))));
    }
}
Output
Common skills:  [Java, Git]
Only team A:    [SQL, Docker]
Short, lower:   [java, sql, git, git, java]
Equal lists?    true

equals() returns true for any two lists with the same elements in the same order, even if they are different classes, as the last line shows.

10. Java 21: List as a Sequenced Collection

Java 21 added the SequencedCollection interface, and List now extends it. Every list gained simple methods for both ends and a reversed view.

SequencedListDemo.java
import java.util.ArrayList;
import java.util.List;

public class SequencedListDemo {
    public static void main(String[] args) {
        // Java 21: List is a SequencedCollection
        List<String> steps = new ArrayList<>(List.of("build", "test"));

        steps.addFirst("checkout");
        steps.addLast("deploy");

        System.out.println("Steps:      " + steps);
        System.out.println("getFirst(): " + steps.getFirst());
        System.out.println("getLast():  " + steps.getLast());
        System.out.println("reversed(): " + steps.reversed());
    }
}
Output
Steps:      [checkout, build, test, deploy]
getFirst(): checkout
getLast():  deploy
reversed(): [deploy, test, build, checkout]

Before Java 21 you had to write list.get(0) and list.get(list.size() - 1). reversed() returns a view, so it does not copy the list.

11. Which List Should You Choose?

  • Most of the time: ArrayList.
  • Fixed data that never changes: List.of(...).
  • Adding and removing at both ends: ArrayDeque (if you do not need indexes) or LinkedList.
  • Shared between threads, mostly read: CopyOnWriteArrayList.
  • Shared between threads, many writes: Collections.synchronizedList(new ArrayList<>()).
  • Unique elements: not a list at all; use a Set.

12. List vs Set

FeatureListSet
DuplicatesAllowedNot allowed
OrderInsertion order, with indexesDepends on the class (none, insertion or sorted)
Access by indexYes, get(i)No
contains() speedO(n)O(1) for HashSet
Main classesArrayList, LinkedListHashSet, LinkedHashSet, TreeSet

13. Best Practices and Common Mistakes

  • Program to the interface: use List for variables, parameters and return types.
  • Do not use raw types: List list = new ArrayList(); loses type safety.
  • Return an empty list instead of null, for example List.of().
  • Do not call add() on List.of() or Arrays.asList(); it throws UnsupportedOperationException.
  • Do not remove inside a for-each loop; use removeIf() or an iterator.
  • Watch remove(int) vs remove(Object) on a List<Integer>.
  • Avoid get(i) loops on a LinkedList; they are O(n²).

14. Summary

  • List is an ordered, index-based collection that allows duplicates.
  • Main implementations: ArrayList (default), LinkedList, CopyOnWriteArrayList, and the legacy Vector and Stack.
  • List.of(), Arrays.asList() and new ArrayList<>() differ in whether the list can change.
  • subList() returns a view, and ListIterator can move both ways and edit the list.
  • Java 21 added getFirst(), getLast(), addFirst(), addLast() and reversed().

Related Tutorials

Reference: Java SE 21 API: java.util.List