🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
java.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)
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);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));
}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);
}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 implementationStandardize 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
Same message format via AbstractLst.subList and Arrays.copyOfRange.
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.