Home Java ArrayIndexOutOfBounds: Fix Java Index Errors
Beginner 5 min · September 23, 2026

ArrayIndexOutOfBounds: Fix Java Index Errors

Fix ArrayIndexOutOfBoundsException fast: loop with < not <=, guard empty arrays, and traverse with enhanced-for by default...

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.

Follow
Production
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
Before you start⏱ 8 min
  • Basic Java arrays and loops
  • Reading stack traces
  • A JDK to compile examples
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • ArrayIndexOutOfBoundsException means an index fell outside 0 to length-1: index equals length, negative math, or any access on an empty array
  • Fix loop bounds first: use i < arr.length, never <=, and compute last as length-1 explicitly
  • Guard arr[0] and arr[arr.length-1] with an emptiness check so zero-length arrays can't reach them
  • Prevent the class with enhanced-for for reads and Objects.checkIndex for computed indexes
✦ Definition~90s read
What is Java ArrayIndexOutOfBounds Fix?

ArrayIndexOutOfBoundsException is an unchecked exception in java.lang thrown when code indexes an array outside its valid range of 0 to length-1. Reading arr[10] from a length-10 array throws; so does arr[-1] and arr[0] on a zero-length array. It extends IndexOutOfBoundsException, and its message names both the guilty index and the array length — Index 10 out of bounds for length 10 — which usually identifies the arithmetic error on sight.

Think of hotel rooms numbered 0 to 9 — ten rooms, but there is no room 10.

The classic producers form a familiar lineup. Loop conditions with <= instead of < run one iteration too many. Caching length in a variable that goes stale after the array is replaced. Indexing a second array with the first array's length when the two differ.

Subtracting without flooring at zero, producing -1. And accessing element zero of an empty array returned by a search or split with no matches. Each is a mismatch between assumed size and actual size.

Two relatives help frame it. String.charAt with a bad index throws StringIndexOutOfBoundsException instead — same idea, different class. List.get with a bad index throws IndexOutOfBoundsException directly. The professional response is uniform: valid indexes live in [0, length), loops use <, computed indexes get checked with Objects.checkIndex, and pure traversal uses enhanced-for so no index exists to get wrong.

Plain-English First

Think of hotel rooms numbered 0 to 9 — ten rooms, but there is no room 10. Asking for room 10 gets you a stare from the clerk: that's ArrayIndexOutOfBoundsException. Programmers keep asking for room 10 because they count ten rooms and forget numbering starts at zero. The fix is counting like the clerk: last room is always total minus one, and an empty hotel has no rooms at all.

The trace says ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 10, and line 43 reads data[i]. You counted ten elements, the loop ran ten times — so why is it asking for a slot that doesn't exist? Because valid indexes run 0 to 9, and your loop's final iteration asked for 10. Off by one. The most written bug in programming history, wearing its clearest error message.

What makes this exception deceptively tricky isn't the message — it's excellent — but the distance between the message and the cause. The index math that produced 10 might live three lines up, or in a size variable that changed, or in a second array with a different length than the one you looped over. Reading the number is step one; tracing where it was born is the actual fix.

This guide covers every shape: the == length classic, <= versus < loops, empty-array access, parallel arrays of mismatched length, negative indexes from bad arithmetic, and ragged 2D arrays. You'll get guard patterns, the enhanced-for escape hatch, and tests that pin boundaries. By the end, index errors die at write time instead of in production logs.

Index == Length: Why Ten Slots End at Nine

Arrays are zero-based: a length-10 array owns indexes 0 through 9, and index 10 belongs to nobody. The message Index 10 out of bounds for length 10 states this precisely — the code asked for one past the last slot. This happens when an index counts items (1-based thinking) instead of slots, when length is used as an index directly, or when a size variable from a different collection drives this array's access.

The one-line shape arr[arr.length] looks obviously wrong in isolation, yet it ships inside real code wearing disguises: last = list.size() then arr[last], or buffer[pos] where pos was incremented past the fill point. Any index derived from a count rather than a position deserves suspicion. The last valid index is always length - 1 — write that expression explicitly wherever you mean the tail.

The snippet shows the failure and its three cures: explicit length-1 for tail access, a < loop for traversal, and Objects.checkIndex for computed indexes. The last one is underused gold: it validates and returns the index in one call, throwing with a precise message when logic errs. The checkIndex call is underused gold: it validates and returns the index in one call with a precise message on failure. Write the tail expression explicitly wherever you mean the last slot of an array.

io/thecodeforge/errors/TailAccess.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Objects;

public final class TailAccess {
    public static int last(int[] arr) {
        if (arr.length == 0) {
            throw new IllegalArgumentException("array must not be empty");
        }
        return arr[arr.length - 1]; // tail is length-1, never length
    }

    public static int at(int[] arr, int computed) {
        int i = Objects.checkIndex(computed, arr.length); // loud on bad math
        return arr[i];
    }

    public static int sum(int[] arr) {
        int total = 0;
        for (int i = 0; i < arr.length; i++) { // < not <=
            total += arr[i];
        }
        return total;
    }
}
📊 Production Insight
A ring buffer read buffer[writePos] threw whenever the buffer filled exactly — writePos equaled capacity. The tail expression needed a modulo, not length. Rule: any index derived from a count gets a bounds check before it touches the array.
🎯 Key Takeaway
Valid indexes are 0 to length-1; length itself is never valid.
Write arr[arr.length - 1] explicitly for tail access.
Objects.checkIndex validates computed indexes with precise errors.

Loop Bounds: <= Versus <, Settled Forever

The canonical indexed loop is for (int i = 0; i < arr.length; i++) and any deviation needs justification. The <= variant runs length + 1 iterations, and its final arr[length] access throws — every time, deterministically. It survives review because readers see length and think complete, forgetting the start at zero already counts the first element. Drill the equivalence: i < length visits exactly indexes 0..length-1, which is every slot once.

Starting at 1 with <= length is the same bug in a mirror: it skips index 0 and throws at length. Code ported from 1-based languages carries this shape. Translate on sight: start 0, bound <, step 1. Backwards loops need care too — for (int i = arr.length - 1; i >= 0; i--) is correct; starting at length throws immediately.

When the loop transforms or filters, the output index can outrun the output array even with a correct read bound. Writing results compactly needs its own counter incremented only on writes. The snippet shows the correct loop family plus the separate-write-counter pattern that fixes the most common transformation bug. Drill the equivalence until it is reflex: i < length visits exactly indexes 0 to length-1, which is every slot once. Code ported from 1-based languages carries the mirrored shape, so translate it on sight.

io/thecodeforge/errors/LoopBounds.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final class LoopBounds {
    public static int[] evens(int[] arr) {
        int[] tmp = new int[arr.length];
        int w = 0; // write counter: only advances on kept items
        for (int i = 0; i < arr.length; i++) { // < covers 0..length-1
            if (arr[i] % 2 == 0) {
                tmp[w++] = arr[i];
            }
        }
        int[] out = new int[w];
        for (int i = 0; i < w; i++) {
            out[i] = tmp[i];
        }
        return out;
    }

    public static void reverse(int[] arr) {
        for (int i = arr.length - 1; i >= 0; i--) {
            System.out.println(arr[i]);
        }
    }
}
⚠ Treat <= in Index Loops as a Defect
A <= bound on an index loop runs one past the end, every time. During review, challenge each <= against an array until it proves itself — or rewrite with < and move on.
📊 Production Insight
The 2.4M-record corruption in this article's incident was a single <= that review had waved through twice. Rule: grep '<=.*length' before every release touching batch code — it takes seconds and catches this exact bug.
🎯 Key Takeaway
i < length visits every slot exactly once — memorize it.
1-based starts and <= bounds are the same bug mirrored.
Transformation loops need a separate write counter for outputs.

Empty Arrays: arr[0] on Nothing

Zero-length arrays are legal objects with no valid indexes — even arr[0] throws. They arrive from searches with no matches, splits of empty strings, filters that reject everything, and varargs calls with no arguments. Code that grabs the first element optimistically works for years until the first empty result, then throws in a place that assumed non-emptiness.

Guard first-element access with an explicit length check carrying a message or a fallback. Which one depends on meaning: empty search results might legitimately return Optional.empty, while an empty batch at a stage that requires data should throw IllegalArgumentException naming the stage. The worst choice is catching the bounds exception after the fact — checking length is clearer, cheaper, and reviewable.

Varargs deserve a special note: method(String... args) with zero arguments gives a real empty array, not null. Any args[0] without a length check is a crash waiting for the first bare call. The snippet shows the guard trio: first-element access, varargs safety, and split-result handling — the three empty-array shapes you'll actually meet. The worst choice is catching the bounds exception after the fact; checking length is clearer, cheaper, and reviewable. Any args[0] without a length check is a crash waiting for the first bare call.

io/thecodeforge/errors/EmptyGuards.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.Optional;

public final class EmptyGuards {
    public static Optional<String> first(String[] arr) {
        if (arr.length == 0) {
            return Optional.empty(); // empty is a normal answer here
        }
        return Optional.of(arr[0]);
    }

    public static String command(String... args) {
        if (args.length == 0) {
            throw new IllegalArgumentException("usage: command <name>");
        }
        return args[0];
    }

    public static String head(String csv) {
        String[] cells = csv.split(",", -1);
        if (cells.length == 0 || cells[0].isBlank()) {
            throw new IllegalArgumentException("csv needs a first cell, got '" + csv + "'");
        }
        return cells[0];
    }
}
📊 Production Insight
A search endpoint threw on every query with no matches because it indexed result[0] to build the response. Empty results are normal traffic, not errors. Rule: first-element access always pairs with a length check or Optional return.
🎯 Key Takeaway
Zero-length arrays have no valid indexes — not even zero.
Guard first-element access with length checks or Optional returns.
Varargs with no arguments is an empty array, never null.

Enhanced-For: Delete the Index, Delete the Bug

Most index bugs exist because an index exists. When traversal needs no position — summing, printing, filtering, mapping — enhanced-for removes the variable that goes wrong: for (int v : arr) can't overshoot, can't use <=, can't mismatch lengths. The compiler writes a correct index or iterator underneath, and an entire bug class evaporates. This is the highest-leverage habit in the article: default to enhanced-for, reach for indexed loops only when you need positions.

Indexed loops stay necessary in four cases: writing to positions, walking two arrays in parallel, needing the index value itself, and iterating backwards with writes. In those cases keep the loop minimal — bound < length, body uses the index plainly — and extract anything clever into helpers. Cleverness inside indexed loops is where <= sneaks back in.

Parallel arrays deserve a warning of their own: two arrays walked by one index require equal lengths, and filters upstream love to break that invariant silently. Prefer a single array of records over parallel arrays wherever possible. The snippet shows the safe traversal default and the guarded parallel walk for the cases you can't restructure yet. Make enhanced-for the default and force indexed loops to justify themselves in comments during review. An entire bug class evaporates when no index variable exists to get wrong.

io/thecodeforge/errors/Traverse.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public final class Traverse {
    public static int sum(int[] arr) {
        int total = 0;
        for (int v : arr) { // no index, no overshoot possible
            total += v;
        }
        return total;
    }

    public static void dot(int[] a, int[] b) {
        if (a.length != b.length) {
            throw new IllegalArgumentException(
                    "parallel arrays differ: " + a.length + " vs " + b.length);
        }
        long total = 0;
        for (int i = 0; i < a.length; i++) {
            total += (long) a[i] * b[i];
        }
        System.out.println(total);
    }
}
📊 Production Insight
A team banned indexed reads in review except for writes and parallel walks. Index exceptions fell to zero in six months while readability scores rose. Rule: make enhanced-for the default and force indexed loops to justify themselves in comments.
🎯 Key Takeaway
Enhanced-for removes the variable that goes wrong — default to it.
Indexed loops remain for writes, parallel walks, and needed positions.
Parallel arrays must assert equal lengths before sharing an index.

Negative Indexes and 2D Ragged Traps

Negative indexes throw just like oversized ones, and they're sneakier because the message shows -1 while readers hunt for overshoot. They come from unclamped subtraction (pos - window), from indexOf returning -1 fed straight into an array, and from modulo on negative dividends. Any index born of arithmetic needs a floor check: if the formula can yield -1, it will, on the input you didn't test.

Two-dimensional arrays add the ragged trap: Java's int[][] is an array of independent rows, each with its own length. Code assuming rectangular shape — looping columns to matrix[0].length for every row — throws on shorter rows. The fix is row-local bounds: matrix[r].length inside the row loop, never a cached width from row zero. Args arrays add a third trap: args[1] without checking args.length first throws on every short command line.

The snippet shows all three cures: indexOf guarded before use, ragged-safe nested loops, and args length checks. These are small, boring, and exactly what production requires. Any index born of arithmetic needs a floor check: if the formula can yield -1, it will, on the input you skipped in tests. Remember that rows are independent arrays, so bind columns to the row-local length every time.

io/thecodeforge/errors/NegativeAndRagged.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final class NegativeAndRagged {
    public static int afterMarker(String[] cells, String marker) {
        int at = java.util.Arrays.asList(cells).indexOf(marker);
        if (at < 0 || at + 1 >= cells.length) {
            throw new IllegalArgumentException("marker '" + marker + "' missing or last");
        }
        return at + 1;
    }

    public static int sumRagged(int[][] m) {
        int total = 0;
        for (int r = 0; r < m.length; r++) {
            for (int c = 0; c < m[r].length; c++) { // row-local bound
                total += m[r][c];
            }
        }
        return total;
    }
}
📊 Production Insight
A CSV parser fed indexOf's -1 into a column array and threw only on files missing an optional header. Optional columns produce -1 regularly. Rule: never feed a search result into brackets without a negativity check.
🎯 Key Takeaway
Negative indexes throw like oversized ones — clamp arithmetic results.
Ragged rows have independent lengths; bind columns to m[r].length.
Check args.length before touching args[1].

Reading Index X out of bounds for length Y

This message is a full diagnosis in one line — learn to parse it instantly. The index tells you what was asked, the length tells you what existed, and their relationship names the bug. Index equals length means overshoot by one: <= loop or length-as-index. Index far beyond length means a stale size, a wrong array, or wild arithmetic. Negative index means unclamped math or an unchecked -1. Empty length with index 0 means an empty array reached first-element code.

The line number completes the picture: open it, list every index expression, and substitute the message's numbers. Usually one expression visibly yields the guilty value. When several could, log each candidate's value on a rerun — one line of output beats ten minutes of staring. Then walk that expression backward to where its inputs were born: the loop bound, the size variable, the arithmetic.

Lock the fix with boundary tests: full array, single element, empty array, and the exact size that failed. The failing size is the regression anchor — encode it as a test name like splitsChunkOf50000 so the next reader knows what it guards. Index bugs that get boundary tests never return. Index bugs that get boundary tests never return, because the exact failing size becomes a permanent regression anchor. Teach the team the three message shapes and half these pages close without escalation.

📊 Production Insight
An on-call read Index 50000 out of bounds for length 50000 and fixed it in four minutes — equals-length means overshoot, overshoot means <=. Rule: teach the team the three message shapes and half these pages close without escalation.
🎯 Key Takeaway
Index == length: overshoot. Far beyond: stale size. Negative: bad math.
Substitute the message numbers into the line's index expressions.
Boundary-test the exact failing size as the regression anchor.
● Production incidentPOST-MORTEMseverity: high

Off-by-One in Batch Split Corrupted 2.4M Records

Symptom
Nightly reconciliation showed 12,000 missing rows per run for 9 days — 108,000 rows total — with no alerts firing. The splitter logged ArrayIndexOutOfBoundsException: Index 50000 out of bounds for length 50000 once per night inside a catch block that skipped the chunk and continued. Monitoring counted the job as successful because it exited zero after swallowing the throw.
Assumption
Analysts assumed the source system was short-shipping rows because counts differed by a clean 12,000. Three tickets went to the vendor, who proved their exports were complete. Nobody read the splitter's warning log, where the exception appeared nightly, because warnings weren't alerted and the job's exit code stayed green.
Root cause
The chunk loop used i <= chunk.length instead of i < chunk.length, so the final iteration read one past the end. The catch for the whole chunk caught the throw, logged a warning, and continued with the next chunk — discarding the final partial chunk of each file. With 12,000 rows in each tail chunk across files, the loss was systematic and silent.
Fix
The bound was corrected to < and the chunk catch was narrowed to per-row handling so a single bad row can't discard a chunk. A row-count reconciliation check was added: source count versus imported count must match or the job fails loudly. Backfill restored all 108,000 rows over two days, and warnings now page after three occurrences.
Key lesson
  • <= in an index loop is guilty until proven innocent. Review every <= against an array as a defect, and let enhanced-for remove the question.
  • Catch blocks that skip work must reconcile counts. A job that exits zero while dropping rows is worse than one that crashes.
  • Alert on warning-rate spikes, not just errors. The signal sat in warnings for 9 days because nobody wired them to a pager.
Production debug guideFive steps that trace the guilty index to its arithmetic.5 entries
Symptom · 01
The message names index and length — find the line
Fix
Open the exact file and line from the trace. List every index expression on it, then print the values: add a temporary log of i and arr.length just above. Reproduce with the same size locally: javac IndexRepro.java && java IndexRepro 50000. The number in the message usually matches the loop bound or length call.
Symptom · 02
You suspect <= versus < in a loop
Fix
Grep loop bounds near arrays: grep -rn '<=.length\|<=.size()' src/main/java. Each hit is a suspect — verify whether the body indexes with the loop variable. Fix to < and run the boundary test with mvn -q -Dtest=ChunkTest test.
Symptom · 03
Two arrays indexed together may differ in length
Fix
Log both lengths before the loop: System.out.println(a.length + " vs " + b.length). If they differ, the loop bound belongs to the shorter — or zip by index with an explicit min. Check producers of each array for filters that shrink one side.
Symptom · 04
The failure depends on the deployed build
Fix
Confirm what's running: jar tf app.jar | grep 'Splitter.class' and javap -c -p com/example/Splitter.class | grep -A 2 'arraylength'. Rebuild with gradle build or mvn -q clean package, rerun the same file, and compare counts before editing bounds.
Symptom · 05
Negative index from arithmetic underflow
Fix
Trace the index math backward: jstack $(pgrep -f app.jar) > /tmp/threads.txt rarely helps here — instead log the computed index expression's inputs. Clamp with Math.max(0, ...) only after understanding the formula; prefer Objects.checkIndex(i, len) to fail loudly on logic errors.
ArrayIndexOutOfBoundsException Causes Compared
Root CauseHow to ConfirmFixPrevention
Loop bound uses <=Index equals length; loop shows <=Change to <; verify 0..length-1Grep <= against length pre-release
Tail access with length as indexarr[arr.length] pattern at the lineUse arr[arr.length-1] with empty guardHelpers for first/last access
Empty array element accessLength 0 with index 0 in messageLength-check or Optional returnEmpty-input tests for every accessor
Mismatched parallel arraysTwo lengths differ when loggedAssert equal lengths; single record arrayReplace parallel arrays with records
Negative computed indexNegative index in message; arithmetic upstreamClamp or reject; checkIndex for loud failValidate search results before indexing
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
iothecodeforgeerrorsTailAccess.javapublic final class TailAccess {Index == Length
iothecodeforgeerrorsLoopBounds.javapublic final class LoopBounds {Loop Bounds
iothecodeforgeerrorsEmptyGuards.javapublic final class EmptyGuards {Empty Arrays
iothecodeforgeerrorsTraverse.javapublic final class Traverse {Enhanced-For
iothecodeforgeerrorsNegativeAndRagged.javapublic final class NegativeAndRagged {Negative Indexes and 2D Ragged Traps

Key takeaways

1
Valid indexes run 0 to length-1; length itself is out of bounds.
2
Loop with <, never <=, unless the bound proves itself.
3
Guard first and last access against empty arrays.
4
Enhanced-for deletes index bugs for pure traversal.
5
Parallel and ragged arrays need per-side length discipline.
6
Read the index-length pair
it usually names the bug.

Common mistakes to avoid

6 patterns
×

Writing <= in indexed loops

Symptom
Index N out of bounds for length N on the final iteration, deterministically, every run.
Fix
Use i < arr.length. Challenge every <= against an array in review until it justifies itself.
×

Indexing arr[0] without an emptiness check

Symptom
Works for years, then throws on the first empty search result, split, or varargs call.
Fix
Guard with a length check, return Optional, or throw a named validation error — never assume non-empty.
×

Driving one array with another's length

Symptom
Throws only when the two arrays differ — after a filter, a partial load, or a schema change.
Fix
Assert equal lengths up front, or restructure into one array of records so lengths can't diverge.
×

Assuming rectangular 2D arrays

Symptom
Column loop bound from row zero throws on shorter rows in ragged data.
Fix
Bind inner loops to m[r].length per row. Never cache a width from the first row.
×

Feeding indexOf -1 into brackets

Symptom
Negative index throws only on inputs missing the searched value — the untested path.
Fix
Check search results for -1 before indexing, with an error naming the missing value.
×

Catching the exception to skip work

Symptom
Chunks or rows silently vanish while the job reports success, as in this article's incident.
Fix
Check bounds before access. Never use this exception for control flow — reconcile counts so skips can't hide.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What are the valid indexes of a length-N array?
Q02JUNIOR
Why does i <= arr.length throw?
Q03SENIOR
How do you safely get the last element?
Q04SENIOR
When is an indexed loop still better than enhanced-for?
Q05SENIOR
How do you handle ragged 2D arrays safely?
Q01 of 05JUNIOR

What are the valid indexes of a length-N array?

ANSWER
0 through N-1. Index N is out of bounds, which is why loops use i < length. The message Index N out of bounds for length N is the signature of an off-by-one overshoot.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
How is this different from IndexOutOfBoundsException?
02
Why does arr[0] throw on my array?
03
Can negative indexes work like Python?
04
Should I catch it around risky access?
05
What does Objects.checkIndex do?
06
My 2D loop throws on some rows only. Why?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.

Follow
Verified
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
🔥

That's Exception Handling. Mark it forged?

5 min read · try the examples if you haven't

Previous
Java NoSuchElementException Fix
13 / 19 · Exception Handling
Next
Java IllegalStateException Fix