forEach() Method
The forEach() method, introduced in Java 8, gives you a clean, readable way to iterate over collections and streams without writing a traditional for loop. It pairs naturally with lambda expressions and method references to produce expressive, concise code.
Why forEach()?
Before Java 8, iterating a collection meant writing a for loop or using an Iterator. Both approaches are fine, but they carry boilerplate. forEach() lets you focus on what you want to do with each element rather than the mechanics of traversal.
import java.util.List;
public class ForEachBasic {
public static void main(String[] args) {
List<String> fruits = List.of("Apple", "Banana", "Cherry");
// Traditional for-each loop
for (String fruit : fruits) {
System.out.println(fruit);
}
// Java 8 forEach() — same result, less noise
fruits.forEach(fruit -> System.out.println(fruit));
}
}
Output:
Apple
Banana
Cherry
Where Does forEach() Come From?
forEach() is defined in two places:
| Source | Signature | Who uses it |
|---|---|---|
java.lang.Iterable | default void forEach(Consumer<? super T> action) | All Collection types (List, Set, Map values, etc.) |
java.util.stream.Stream | void forEach(Consumer<? super T> action) | Streams |
Because it is a default method on Iterable (see Default Methods), every existing collection automatically gained it without any changes to those classes.
The argument is a Consumer<T> — a functional interface with a single method accept(T t) that takes a value and returns nothing.
forEach() on Collections
List
import java.util.List;
public class ListForEach {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.forEach(n -> System.out.println("Square: " + (n * n)));
}
}
Output:
Square: 1
Square: 4
Square: 9
Square: 16
Square: 25
Set
import java.util.Set;
public class SetForEach {
public static void main(String[] args) {
Set<String> languages = Set.of("Java", "Python", "Kotlin");
languages.forEach(lang -> System.out.println(lang.toUpperCase()));
}
}
Note:
Sethas no guaranteed order, so the output order may vary between runs.
Map
Map does not implement Iterable, but it has its own forEach() that takes a BiConsumer<K, V> — a functional interface that accepts two arguments.
import java.util.Map;
public class MapForEach {
public static void main(String[] args) {
Map<String, Integer> scores = Map.of("Alice", 95, "Bob", 82, "Carol", 88);
scores.forEach((name, score) ->
System.out.println(name + " scored " + score));
}
}
Output:
Alice scored 95
Bob scored 82
Carol scored 88
(Order may vary since Map.of() returns an unordered map.)
forEach() on Streams
When combined with the Stream API, forEach() acts as a terminal operation — it triggers the pipeline and consumes each element.
import java.util.List;
public class StreamForEach {
public static void main(String[] args) {
List<String> names = List.of("alice", "bob", "charlie", "dave");
names.stream()
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.forEach(System.out::println);
}
}
Output:
ALICE
CHARLIE
DAVE
Tip: Prefer
forEach()at the end of a stream pipeline for side effects like printing. For transformations, usemap(),filter(), andcollect()instead of forEach with mutation.
Using Method References
Anywhere you pass a lambda that just calls one method, you can replace it with a method reference. This is a common shorthand with forEach().
import java.util.List;
public class MethodRefForEach {
public static void main(String[] args) {
List<String> cities = List.of("Tokyo", "Paris", "Cairo");
// Lambda
cities.forEach(city -> System.out.println(city));
// Equivalent method reference — cleaner!
cities.forEach(System.out::println);
}
}
Both lines produce the same output. Use whichever reads more clearly.
forEach() vs Traditional Loops
| Feature | Traditional for-each loop | forEach() method |
|---|---|---|
| Syntax | Verbose | Concise (lambda / method ref) |
| Works with streams | No | Yes |
| Break / continue | Yes | No — use return inside lambda to skip one element |
| Exception handling | Checked exceptions allowed | Checked exceptions must be wrapped |
| Parallel execution | No (without extra code) | Use parallelStream().forEach() |
| Order guarantee | Yes | Yes for sequential; not guaranteed for parallel |
Warning: You cannot use
breakorcontinueinside aforEach()lambda. If you need early exit, use a traditional loop orStream.anyMatch()/Stream.takeWhile().
Skipping Elements (Simulating continue)
Inside a forEach() lambda, a return statement skips to the next element — it behaves like continue in a loop.
import java.util.List;
public class SkipInForEach {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
// Print only odd numbers
numbers.forEach(n -> {
if (n % 2 == 0) return; // skip even numbers
System.out.println(n);
});
}
}
Output:
1
3
5
Checked Exceptions Inside forEach()
Consumer.accept() does not declare checked exceptions, so you cannot throw one directly. You have two common workarounds.
import java.util.List;
public class ForEachException {
// Wrap the checked exception in a RuntimeException
public static void main(String[] args) {
List<String> filenames = List.of("a.txt", "b.txt");
filenames.forEach(file -> {
try {
processFile(file);
} catch (Exception e) {
throw new RuntimeException("Failed for " + file, e);
}
});
}
static void processFile(String name) throws Exception {
System.out.println("Processing " + name);
}
}
Output:
Processing a.txt
Processing b.txt
Tip: For cleaner code, extract the try/catch into a helper method that wraps checked exceptions, then pass that helper as a method reference.
Parallel forEach
For large datasets, you can use parallelStream() with forEach() to process elements concurrently. Be careful: the order of execution is not guaranteed.
import java.util.List;
public class ParallelForEach {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.parallelStream()
.forEach(n -> System.out.println(Thread.currentThread().getName() + ": " + n));
}
}
Output (example — order varies):
main: 3
ForkJoinPool.commonPool-worker-1: 1
ForkJoinPool.commonPool-worker-2: 5
main: 4
ForkJoinPool.commonPool-worker-1: 2
Warning: Only use
parallelStream()when the operation is stateless and thread-safe. Avoid shared mutable state inside a parallelforEach()— it leads to race conditions.
Under the Hood
When you call forEach() on a standard ArrayList, the JVM calls the overridden implementation in ArrayList itself (not the default from Iterable). ArrayList.forEach() uses a plain index-based loop internally — it avoids creating an Iterator object, which saves a small allocation.
// Pseudocode for ArrayList.forEach
for (int i = 0; i < size; i++) {
action.accept(elementData[i]);
}
The Consumer<T> lambda you pass is compiled into a synthetic class (or a method handle via invokedynamic). At JIT time, the JVM often inlines both the lambda body and the loop, producing machine code nearly identical to a handwritten loop — so there’s no performance penalty for using forEach() in the common case.
For streams, forEach() is a terminal operation that drives the lazy pipeline. Each upstream stage (filter, map, etc.) is fused together during execution to avoid creating intermediate collections. This is what makes stream pipelines memory-efficient.
Related Topics
- Lambda Expressions — the syntax that makes forEach() concise and readable
- Functional Interfaces — understand Consumer and BiConsumer that power forEach()
- Stream API — forEach() as a terminal operation in stream pipelines
- Stream Operations (filter/map/reduce) — what to do before you reach forEach()
- Method References — replace simple lambdas with even cleaner syntax
- Default Methods — how forEach() was added to Iterable without breaking existing code