🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
at com.devinhyderabad.shop.Inventory.lowestStock(Inventory.java:7)
at com.devinhyderabad.shop.InventoryDemo.main(InventoryDemo.java:5)
// Same access on Java 8 printed only the offending index — no length context:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5⚡ Quick Fix Works 80% of the time
Read the two numbers in the message — requested index vs real length — and fix the arithmetic on the failing line.
if (index >= 0 && index < stockCounts.length) {
return stockCounts[index];
}
throw new IllegalArgumentException(
"stock index " + index + ", valid range 0.." + (stockCounts.length - 1));🧠 Why this Happens
Tap to expand the deep technical explanation
Every array load or store compiles to a dedicated bytecode (aaload, iastore, ...) whose execution passes through an implicit bounds check: HotSpot compares the index against the length field embedded in the array object header BEFORE any memory is touched, so an out-of-range access can never read foreign memory — this is Java memory safety working as designed. The check costs almost nothing because the JIT elides it wherever it proves dominance, but semantics demand the throw at the exact instruction. Pre-JDK 9 builds formatted the message inside raw VM code where only the integer index was conveniently available; JDK 9 routed failure through richer message construction, adding the length that turns guesswork into arithmetic.
The HITEC City Parking Spot Analogy:
A parking garage with levels B1–B3 handing you a ticket stamped for level B7: the old barrier just flashed your ticket back at you; the new barrier reads "you asked for B7 — this garage ends at B3."
🔁 How to Reproduce Confirm this is your error
Declare int[] stockCounts = new int[3] and return stockCounts[5]; compile with javac --release 17 and run — the descriptive form prints instantly. Wording verified byte-identical on OpenJDK 17.0.19 and Temurin 25.0.2. The bare-index legacy line requires a JDK <= 8 runtime (not installed locally) and reflects documented behavior.
🛠️ Solutions (5 Ways to Fix)
Decode Index-vs-length and kill the off-by-one
👉 Use this first — on any JDK 9+ the message hands you both facts needed to diagnose without a debugger.
"Index 5 out of bounds for length 3" means valid range was 0..2. The usual suspects: loop condition i <= arr.length instead of <, treating length as the last index instead of length - 1, reading [0] of a possibly-empty array, and mixing rows with columns in matrices. Fix the comparison, not the symptom.
// Before: runs exactly one step too far
for (int i = 0; i <= shelf.length; i++) { total += shelf[i]; }
// After:
for (int i = 0; i < shelf.length; i++) { total += shelf[i]; }Guard computed indices — especially indexOf() returning -1
👉 Use this when the index comes from a lookup rather than a loop counter.
A classic production crash: header.indexOf(":") returns -1 when the delimiter is missing, and fields[-1] explodes with AIOOBE: -1. A negative index here means NOT FOUND, not position-zero-counting-backwards. Any time an index is computed rather than counted, validate it before use.
int pos = header.indexOf(":");
if (pos < 0) {
throw new IllegalArgumentException("malformed header, no ':' in: " + header);
}
parse(fields[pos]);Validate boundaries at public API entry points
👉 Use this when methods accept caller-supplied indices.
Fail fast with both numbers and the valid range instead of letting a raw AIOOBE escape three frames deep. Objects.checkIndex (Java 9+) does exactly this in one call and produces the same descriptive wording in its IndexOutOfBoundsException.
public int lowestStock(int candidate) {
return stockCounts[Objects.checkIndex(candidate, stockCounts.length)];
}Drop manual indexing where order alone matters
👉 Use this during refactors when the index variable exists only to walk the array.
Enhanced-for eliminates the index variable entirely — the bug class disappears with it. IntStream.range keeps positional intent explicit where you truly need the number, with bounds guaranteed by construction.
// No index left to misuse:
for (String label : labels) render(label);
// Deliberate positional access, still safe by construction:
IntStream.range(0, cells.length).forEach(i -> paint(cells[i], i));Test empty inputs before touching element zero
👉 Use this for pipelines where upstream data can arrive empty.
A zero-length array makes even data[0] explode with "Index 0 out of bounds for length 0". Guard emptiness explicitly or route through first-element helpers so the empty case becomes an intentional branch instead of a crash report.
if (rows.length == 0) {
return Report.empty();
}
return Report.of(rows[0]);📋 Version Notes
Message carries ONLY the raw index (e.g. "5") — no length context; debugging meant opening the frame file yourself.
Descriptive form "Index N out of bounds for length M" introduced alongside Objects.checkIndex/checkFromToIndex helpers.
Wording identical since 9 — lab capture byte-compared equal across JDK 17 and JDK 25 runtimes.
No change; enhanced-for and streams remain the structural prevention.
🛡️ How to Prevent This Next Time
Loop with i < arr.length (never <=), prefer enhanced-for when the index itself is unused, run Objects.checkIndex on public APIs accepting indices, and unit-test empty-array edge cases explicitly.