Java 8 forEach with index
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Java 8 forEach does not provide an index parameter. That is not an omission you are supposed to patch over in every case. It is a sign that forEach is designed for element-oriented traversal, not indexed iteration. If your logic truly depends on the index, the best solution is often not "force an index into forEach". The best solution is to use a loop or an IntStream.
The Basic Limitation
This works:
But there is no built-in (index, value) form. So if you need the index, you have to pick another pattern intentionally.
Best Option for Indexed Access: IntStream.range
For Java 8 code that wants stream style and an index, IntStream.range is usually the cleanest answer.
This is explicit about what is happening: you are iterating over indices, then reading each element by index.
If Indexing Is Central, a Classic Loop Is Still Fine
There is no prize for avoiding a normal for loop when it is the clearest tool.
If the real requirement is indexed access, this is often more readable than stream-based alternatives.
The AtomicInteger Trick Works, but It Is Usually a Smell
People often write:
This works in sequential code, but it is usually not the best style. The atomic counter is not there because the problem is atomicity. It is there because forEach does not expose the index. That often makes the code feel more clever than necessary.
So a good rule is:
- use
IntStream.rangeif you want indexed stream style - use a normal loop if that is clearer
- use
AtomicIntegeronly when you have a very specific reason
Be Careful with Parallel Streams
Index-based logic and forEach become especially awkward with parallel streams. Once ordering and side effects matter, many of the normal stream advantages disappear.
That is fine for independent side effects, but it is not a good environment for fake shared-index tricks. If you need ordered indexed behavior, stick with sequential structures.
Think About Why You Need the Index
Sometimes the need for an index is a hint that you actually want a different data shape. Examples:
- pair two lists by position
- generate numbered output
- mutate by position
In those cases, iterating over indices directly is more honest than pretending you are doing element-only functional traversal.
Common Pitfalls
- Expecting Java 8
forEachto provide an index like some other languages do. - Using
AtomicIntegerby default when a simple loop orIntStream.rangewould be clearer. - Mixing indexed logic with parallel streams and shared mutable state.
- Choosing stream style for appearance even when indexed access is the real requirement.
- Forgetting that
items.get(i)is only appropriate when random access is efficient for the collection type.
Summary
- Java 8
forEachdoes not natively expose an index. - '
IntStream.range(0, list.size())is the cleanest indexed stream-style option.' - A classic
forloop is often the clearest answer when index matters. - The
AtomicIntegerworkaround is valid but usually not the best default. - If your logic depends on indices, write code that is honest about iterating over indices.

