forEach runs a piece of code once per element. It arrived with Java 8 as the
functional counterpart to the for-each loop, and choosing between the two is mostly a readability
question rather than a technical one.
The method
List<String> names = List.of("Ana", "Bo", "Cy");
class Demo {
void run(List<String> names) {
names.forEach(name -> System.out.println(name)); // lambda
names.forEach(System.out::println); // method reference — same thing
}
}
It takes a Consumer — one argument in, nothing out. That shape is the whole story:
forEach exists to cause side effects, because it cannot return anything. If you want a
result, you want map, filter or collect instead.
It is declared on Iterable, so every collection has it, and separately on
Stream and on Map — three different declarations of the same idea, which is
why the map version can take two arguments while the others take one.
On lists and sets
class Demo {
void run() {
List<String> names = List.of("Ana", "Bo");
Set<Integer> ids = Set.of(1, 2, 3);
names.forEach(name -> System.out.println("hello " + name));
ids.forEach(System.out::println);
// Order: a List iterates in order. A HashSet does not promise any.
new TreeSet<>(ids).forEach(System.out::println); // 1, 2, 3 — sorted
}
}
On maps — the two-argument version
This is the one that saves the most typing. Map.forEach takes a
BiConsumer, handing you the key and the value directly instead of an
Entry to unpack:
class Demo {
void run() {
Map<String, Integer> stock = Map.of("apples", 10, "pears", 4);
// The old way
for (Map.Entry<String, Integer> entry : stock.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
// The same thing
stock.forEach((item, count) -> System.out.println(item + " = " + count));
}
}
On streams
class Demo {
void run() {
List<String> names = List.of("Ana", "Bo", "Christopher");
names.stream()
.filter(n -> n.length() > 2)
.map(String::toUpperCase)
.forEach(System.out::println); // ANA, CHRISTOPHER
// forEachOrdered guarantees encounter order even on a parallel stream
names.parallelStream().forEachOrdered(System.out::println);
}
}
On a sequential stream forEach already runs in order. On a parallel one it does not,
and forEachOrdered is how you ask for order back — at the cost of most of the
parallelism, which is usually a sign the stream should not have been parallel.
What it cannot do
Three limitations, and each one is a reason to use a plain loop instead:
class Demo {
void run() {
List<String> names = List.of("Ana", "Bo", "Cy");
// 1. You cannot break out of it. This runs to the end regardless.
names.forEach(name -> {
if (name.equals("Bo")) {
return; // skips THIS element only — like `continue`
}
System.out.println(name);
});
// A loop can stop early:
for (String name : names) {
if (name.equals("Bo")) break;
System.out.println(name);
}
}
}
The return above is the mistake people make: it looks like break and
behaves like continue, because it returns from the lambda, not from the loop. There is
no way to stop a forEach early. If you need that, use a loop — or
anyMatch, findFirst or takeWhile, which short-circuit
properly.
2. You cannot modify the collection while iterating. Same rule as a for-each loop, same exception:
class Demo {
void run() {
List<String> names = new ArrayList<>(List.of("Ana", "Bo"));
// names.forEach(n -> { if (n.startsWith("B")) names.remove(n); });
// -> ConcurrentModificationException
names.removeIf(n -> n.startsWith("B")); // the right way
System.out.println(names); // [Ana]
}
}
3. You cannot mutate a local variable from inside it, because a lambda can only capture effectively-final variables. A counter is the usual thing people reach for, and the answer is not to fight the rule:
class Demo {
void run() {
List<String> names = List.of("Ana", "Bo", "Christopher");
// int count = 0;
// names.forEach(n -> { if (n.length() > 2) count++; }); // does not compile
long count = names.stream().filter(n -> n.length() > 2).count();
System.out.println(count); // 2
}
}
Does it cost anything?
A fair question, since forEach creates a lambda object where a loop creates nothing.
In practice the answer is no, for two reasons worth knowing rather than taking on faith.
First, a lambda that captures nothing — System.out::println, or n ->
n.trim() — is instantiated once and reused, not allocated per call. Only a capturing lambda
creates an object, and even then it is one object for the whole loop, not one per element.
Second, the JIT compiler inlines both forms into much the same machine code once the method is hot. For a collection of any realistic size the difference is unmeasurable, and for a collection small enough that it might matter, the whole operation is already free.
So choose on readability. The one real performance caveat is the opposite of the usual worry:
parallelStream().forEach(...) is genuinely slower than a loop for small collections,
because coordinating threads costs more than the work being coordinated.
Choosing between them
| Use | When |
|---|---|
forEach | the body is one short action, especially a method reference |
forEach on a map | you want key and value without unpacking an
Entry |
| for-each loop | you need break, the index, or a body longer than a couple
of lines |
| neither | you are building a result — use map/collect, not a
side effect |
That last row is the one worth internalising. Code like
stream().forEach(list::add) is a pipeline pretending to be a loop; it breaks under
parallelism and reads worse than stream().toList(). If a forEach is
filling something in, it is the wrong tool.
Next
Default and static interface
methods is next — how forEach was added to Iterable without breaking
every class that already implemented it.