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.
nullallowed by most implementations (but not byList.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.
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.
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());
}
}
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
| Class | Stored as | Best for |
|---|---|---|
| ArrayList | Resizable array | The default choice: fast reading by index and fast adding at the end. |
| LinkedList | Doubly linked nodes | Adding and removing at both ends; also a queue or deque. |
| Vector | Resizable array, synchronized | Legacy code only. |
Stack | Extends Vector | Legacy code only; use ArrayDeque for stacks. |
CopyOnWriteArrayList | Array copied on every write | Lists 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:
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);
}
}
}
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.
| Code | Add or remove | Change elements (set) | Allows null |
|---|---|---|---|
new ArrayList<>(...) | Yes | Yes | Yes |
Arrays.asList(...) | No (fixed size) | Yes, and it writes to the array | Yes |
List.of(...) (Java 9+) | No | No | No |
List.copyOf(c) (Java 10+) | No | No | No |
Collections.unmodifiableList(list) | No | No | Yes (read-only view of another list) |
stream.toList() (Java 16+) | No | No | Yes |
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));
}
}
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]
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:
| Method | Description |
|---|---|
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, reversed | Sequenced 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.
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);
}
}
indexOf(85): 1 lastIndexOf(85): 3 contains(100): false subList(1, 4): [85, 60, 85] After clear(): [72, 91, 47] Sorted: [47, 72, 91]
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().
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();
}
}
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.
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))));
}
}
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.
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());
}
}
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) orLinkedList. - 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
| Feature | List | Set |
|---|---|---|
| Duplicates | Allowed | Not allowed |
| Order | Insertion order, with indexes | Depends on the class (none, insertion or sorted) |
| Access by index | Yes, get(i) | No |
contains() speed | O(n) | O(1) for HashSet |
| Main classes | ArrayList, LinkedList | HashSet, LinkedHashSet, TreeSet |
13. Best Practices and Common Mistakes
- Program to the interface: use
Listfor 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 exampleList.of(). - Do not call
add()onList.of()orArrays.asList(); it throwsUnsupportedOperationException. - Do not remove inside a for-each loop; use
removeIf()or an iterator. - Watch
remove(int)vsremove(Object)on aList<Integer>. - Avoid
get(i)loops on a LinkedList; they are O(n²).
14. Summary
Listis an ordered, index-based collection that allows duplicates.- Main implementations:
ArrayList(default),LinkedList,CopyOnWriteArrayList, and the legacyVectorandStack. List.of(),Arrays.asList()andnew ArrayList<>()differ in whether the list can change.subList()returns a view, andListIteratorcan move both ways and edit the list.- Java 21 added
getFirst(),getLast(),addFirst(),addLast()andreversed().
Related Tutorials
- Collection Interface in Java
- ArrayList in Java
- LinkedList in Java
- Set Interface in Java
- Difference between List and Set
Reference: Java SE 21 API: java.util.List
Frequently Asked Questions (FAQ)
Q1. What is the List interface in Java?
List is an interface in java.util for ordered collections. Each element has an index starting at 0, duplicates are allowed, and you can add, get, replace and remove elements by position. Common implementations are ArrayList and LinkedList.
Q2. Can I create an object of the List interface?
No, an interface cannot be instantiated with new List<>(). Create an implementation such as new ArrayList<>(), or use a factory method such as List.of(...), and store it in a List variable.
Q3. Which classes implement the List interface?
In the JDK: ArrayList, LinkedList, Vector, Stack and CopyOnWriteArrayList, plus abstract helpers such as AbstractList. The lists returned by List.of(), Arrays.asList() and Collections.unmodifiableList() are private implementations.
Q4. Why should I write List<String> list = new ArrayList<>() instead of ArrayList<String>?
It keeps your code independent of the implementation. You can switch to another list class by changing one line, and methods that accept a List work with any kind of list the caller has.
Q5. What is the difference between List.of() and Arrays.asList()?
List.of()is fully unmodifiable and rejectsnull.Arrays.asList()has a fixed size but allowsset(), acceptsnull, and is a view of the array, so changes appear in both.
Neither supports add() or remove().
Q6. What is the difference between List and Set?
A List keeps insertion order, allows duplicates and supports access by index. A Set stores only unique elements and has no indexes; its order depends on the class (HashSet has none, LinkedHashSet keeps insertion order, TreeSet sorts).
Q7. Does a List allow null elements?
ArrayList, LinkedList, Vector and Arrays.asList() allow null. Lists created with List.of() or List.copyOf() do not, and throw NullPointerException.
Q8. What is a ListIterator and how is it different from an Iterator?
A ListIterator works only with lists. In addition to hasNext(), next() and remove(), it has hasPrevious(), previous(), nextIndex(), previousIndex(), set() and add(), so it can walk backwards and change the list while iterating.
Q9. Does subList() create a new list?
No. It returns a view backed by the original list. Changes made through the view appear in the original, and structural changes to the original make the view unusable. Copy it with new ArrayList<>(list.subList(a, b)) if you need an independent list.
Q10. How do I sort a List?
list.sort(null); // natural order
list.sort(Comparator.reverseOrder()); // reverse order
people.sort(Comparator.comparing(Person::getAge)); // by a field
Collections.sort(list) does the same as list.sort(null).
Q11. How do I make a List read-only?
Use List.copyOf(list) for an unmodifiable copy, or Collections.unmodifiableList(list) for a read-only view that still reflects later changes to the original list.
Q12. How do I make a List thread-safe?
Use Collections.synchronizedList(new ArrayList<>()) and synchronize on it while iterating, or use CopyOnWriteArrayList when reads are much more frequent than writes.
Q13. How do I remove duplicates from a List?
List<String> unique = new ArrayList<>(new LinkedHashSet<>(list));
// or
List<String> unique2 = list.stream().distinct().toList();
Both keep the first occurrence of each element in the original order.
Q14. When are two lists equal?
When they contain the same elements in the same order, compared with equals(). The list classes do not matter, so an ArrayList and a LinkedList with the same elements are equal.
Q15. How do I convert a List to an array and back?
String[] arr = list.toArray(new String[0]);
List<String> back = new ArrayList<>(Arrays.asList(arr));
Q16. What new List methods were added in Java 21?
Through SequencedCollection: addFirst(), addLast(), getFirst(), getLast(), removeFirst(), removeLast() and reversed(). Unmodifiable lists throw UnsupportedOperationException for the methods that change the list.
Q17. Why does list.remove(1) remove the wrong element from a List<Integer>?
Because remove(1) calls remove(int index) and removes the element at index 1. To remove the value 1, use list.remove(Integer.valueOf(1)).
Q18. What happens if I use an invalid index?
Methods such as get(), set() and remove(int) throw IndexOutOfBoundsException when the index is less than 0 or not less than size(). For add(index, e), the index may also equal size(), which adds at the end.
Q19. Is List ordered or sorted?
Ordered, not sorted. A list keeps elements in the order you put them in. It is sorted only after you call sort(), and later additions are not placed in sorted position automatically. For an always-sorted collection, consider a TreeSet or a PriorityQueue.
Q20. Was Vector deprecated?
No. Vector is not deprecated; it is still a supported part of the JDK. It is considered a legacy class, and new code usually uses ArrayList or a concurrent list instead.