subList()
Returns a live view of a range of a list, for paging, chunking and range deletes, with the rules that keep it valid.
List<E> subList(int fromIndex, int toIndex)- Added in Java Java 1.2
Parameters
| Parameter | Type | Description |
|---|---|---|
$fromIndex | int | First index of the range, inclusive. |
$toIndex | int | End of the range, exclusive. Must be at most size(). |
Return value
List<E>A view of the elements from fromIndex (inclusive) to toIndex (exclusive), backed by the original list.
Errors
IndexOutOfBoundsException if fromIndex < 0, toIndex > size() or fromIndex > toIndex (the JDK lists, including ArrayList, LinkedList and List.of, throw IllegalArgumentException for the last case). Using the view after the original list has been structurally modified elsewhere may throw ConcurrentModificationException.
subList(from, to) returns the elements from index from up to, but not including, to. So subList(0, 3) has three elements, and subList(i, i) is empty. The half-open range matches String.substring and makes page arithmetic simple: page p of size n is subList(p * n, p * n + n), clamped to size().
A view, not a copy
The result does not copy anything. It is a window onto the original list. Reading it reads the original; set through it changes the original; add, remove and clear through it change the original's size. That makes list.subList(from, to).clear() the idiomatic way to delete a range in one shift.
The flip side: if the original list is structurally changed by anything other than the view (an add or remove on the parent), the view becomes invalid. On ArrayList the next use of the view throws ConcurrentModificationException. Plain set calls on the parent are not structural and do not invalidate it.
Copy before you keep it
Because it is a view, a sub-list also keeps the entire backing list reachable. Returning bigList.subList(0, 10) from a cache or storing it in a field holds on to all of bigList in memory, and leaves the result exposed to later changes. When the slice outlives the method, copy it: List.copyOf(list.subList(a, b)) or new ArrayList<>(...).
Bounds
toIndexmay equalsize(); it may not exceed it. Clamp withMath.min(to, list.size())when the range comes from a client.- A negative
fromIndexthrowsIndexOutOfBoundsException. fromIndex > toIndexis an error too. The interface documentsIndexOutOfBoundsException, but the JDK lists (ArrayList,LinkedList,List.of,Arrays.asList) throwIllegalArgumentException, so catchRuntimeExceptionor, better, validate the range first.
For data held in a database, page with LIMIT and OFFSET or keyset pagination in the query. subList paging is for data already in memory: a cached list, a merged result, or batching rows before a bulk insert.
Example
When to use it
- Paging through a cached or merged result set held in memory.
- Splitting imported rows into fixed-size batches for JDBC batch inserts.
- Trimming an in-memory activity log to its newest entries with subList(...).clear().
Frequently asked questions
Does subList create a copy?
No. It returns a view backed by the original list. Changes through either one are visible in the other, and structural changes to the original invalidate the view. Copy it with new ArrayList<>(...) or List.copyOf(...) if you need an independent list.
Why does subList throw ConcurrentModificationException?
The original list was structurally modified (an element added or removed) after the sub-list was created, not through the sub-list. Create the sub-list after the changes, or copy it before changing the parent.
How do I split a list into chunks in Java?
Loop in steps of the chunk size and take list.subList(i, Math.min(i + size, list.size())) on each step.
More examples
A safe page helper
Clamping both ends means a page number beyond the data returns an empty list rather than throwing. List.copyOf detaches the result from the source.
<T> List<T> page(List<T> all, int pageNumber, int size) {
int from = Math.min(pageNumber * size, all.size());
int to = Math.min(from + size, all.size());
return List.copyOf(all.subList(from, to));
}
void main() {
List<String> items = List.of("a", "b", "c", "d", "e");
IO.println(page(items, 0, 2));
IO.println(page(items, 2, 2));
IO.println(page(items, 9, 2));
}
// Output:
// [a, b]
// [e]
// []A view goes stale when the parent changes
Adding to the parent is a structural change, so the ArrayList view refuses to be used afterwards. The copy taken earlier is independent.
void main() {
List<String> log = new ArrayList<>(List.of("a", "b", "c"));
List<String> lastTwo = log.subList(1, 3);
List<String> saved = List.copyOf(lastTwo);
log.add("d");
try {
IO.println(lastTwo.size());
} catch (ConcurrentModificationException e) {
IO.println("view invalid: ConcurrentModificationException");
}
IO.println("copy still fine: " + saved);
}
// Output:
// view invalid: ConcurrentModificationException
// copy still fine: [b, c]Chunking rows for batch inserts
Each batch is a cheap view used immediately, so no copy is needed. The Math.min clamp produces the shorter final chunk.
void main() {
List<String> rows = List.of("r1", "r2", "r3", "r4", "r5", "r6", "r7");
int batchSize = 3;
for (int i = 0; i < rows.size(); i += batchSize) {
List<String> batch = rows.subList(i, Math.min(i + batchSize, rows.size()));
IO.println("insert " + batch);
}
}
// Output:
// insert [r1, r2, r3]
// insert [r4, r5, r6]
// insert [r7]
Discussion