🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOGjava.lang.IllegalArgumentException: fromIndex(2) > toIndex(1) at java.base/java.util.ImmutableCollections$AbstractImmutableList.subListRangeCheck(ImmutableCollections.java:297) at java.base/java.util.ImmutableCollections$AbstractImmutableList.subList(ImmutableCollections.java:287)

⚡ Quick Fix Works 80% of the time

Clamp both endpoints before slicing, then guard the empty case.

int lo = Math.max(0, i); int hi = Math.min(list.size(), i + n); List<T> win = lo < hi ? list.subList(lo, hi) : List.of();

🧠 Why this Happens

Tap to expand the deep technical explanation

The three checks live in subListRangeCheck (and equivalents in Arrays.copyOfRange). Endpoint-validity failures mean an index does not EXIST, hence IndexOutOfBoundsException. Order failure means both indexes exist but the interval is empty-backwards - a caller-logic problem, hence IllegalArgumentException. The message embeds both numbers so you can spot the reversal instantly.

The HITEC City Parking Spot Analogy:

A taxi itinerary with pickup scheduled after drop-off: both addresses exist, but the plan is impossible.

🔁 How to Reproduce Confirm this is your error

List<Integer> l = List.of(10, 20, 30); l.subList(2, 1); // IllegalArgumentException: fromIndex(2) > toIndex(1)

🛠️ Solutions (5 Ways to Fix)

Solution 1✓ Most common cause

Swap the arguments if reversed

👉 Use this if debugging shows variables passed in the wrong order.

Most occurrences are literal argument swaps (to, from) vs (from, to); fixing call sites restores correct semantics with no behavior trade-off.

// BROKEN: page(items, endIdx, startIdx) List<T> page = items.subList(startIdx, endIdx);
Solution 2

Clamp sliding windows before slicing

👉 Use this if windows are computed as (i, i + n) near boundaries.

Clamping lo/hi keeps every slice in-bounds, and the lo < hi test skips empty windows that would otherwise throw.

for (int i = 0; i < data.size(); i += step) { int hi = Math.min(data.size(), i + window); process(data.subList(i, hi)); }
Solution 3

Return an explicit empty result for inverted ranges

👉 Use this if callers may legitimately pass start == end or past-end windows.

Documenting "empty window yields List.of()" converts a crash into a defined edge case for batch processors.

static <T> List<T> safeSlice(List<T> src, int from, int to) { int lo = Math.max(0, from), hi = Math.min(src.size(), to); return lo >= hi ? List.of() : src.subList(lo, hi); }
Solution 4

Centralize one validated slice utility

👉 Use this if multiple modules copy ranges.

One audited helper (with tests covering reversed args) prevents each new call site from re-inventing range bugs.

Ranges.slice(rows, from, toExclusive) // single implementation
Solution 5

Standardize exclusive-end naming

👉 Use this if teams mix from/to conventions across codebases.

Naming parameters toExclusive/fromInclusive removes ambiguity about whether "to" includes its index - the root of many reversals.

slice(list, fromInclusive, toExclusive) // convention documented

📋 Version Notes

Java 8

Same message format via AbstractLst.subList and Arrays.copyOfRange.

Java 9+

ImmutableCollections factories enforce identical checks; wording unchanged through Java 21+.

🛡️ How to Prevent This Next Time

Adopt one range convention (inclusive-exclusive) and clamp at utility level; never hand subList raw computed values without bounds checks.