Java Stream forEach() Method
Java Stream forEach() Method
forEach() runs a given action once for every element in a stream or a collection, taking a Consumer<T> and returning nothing at all. It exists to replace the visible loop — the index variable, the braces, the explicit iteration — with a single declarative call that says exactly what it does: run this action, for each element. It is almost always the last thing in a pipeline, since once forEach() runs on a stream, there is nothing left of that stream to chain anything else onto.
What Is forEach()?
Two separate forEach() methods exist in the JDK and get confused constantly. Iterable.forEach(Consumer<? super T> action) is a default method added directly to Iterable in Java 8, available on any List, Set, or other collection without calling .stream() first. Stream.forEach(Consumer<? super T> action) is a genuine terminal operation on Stream, ending whatever pipeline was built before it. Both take a Consumer and iterate through elements, running the given action on each one.
A third method, forEachOrdered(), exists specifically for streams — it guarantees the action runs in the stream's encounter order even on a parallel stream, where plain forEach() explicitly makes no such guarantee.
Why forEach() Was Introduced
Running the same action against every element of a collection used to mean a visible loop, complete with an index or an iterator variable that had nothing to do with the actual logic being performed.
1// File: BeforeForEach.java
2import java.util.*;
3
4public class BeforeForEach {
5 record StockItem(String sku, int quantity) {}
6
7 public static void main(String[] args) {
8 List<StockItem> inventory = List.of(
9 new StockItem("SKU-1", 5),
10 new StockItem("SKU-2", 40),
11 new StockItem("SKU-3", 2)
12 );
13
14 for (int i = 0; i < inventory.size(); i++) {
15 StockItem item = inventory.get(i);
16 System.out.println("Checking " + item.sku());
17 }
18 }
19}Output:
Checking SKU-1
Checking SKU-2
Checking SKU-3
forEach() keeps the exact same action but drops the index entirely, since the loop mechanics were never the interesting part of the code to begin with.
1// File: AfterForEach.java
2import java.util.*;
3
4public class AfterForEach {
5 record StockItem(String sku, int quantity) {}
6
7 public static void main(String[] args) {
8 List<StockItem> inventory = List.of(
9 new StockItem("SKU-1", 5),
10 new StockItem("SKU-2", 40),
11 new StockItem("SKU-3", 2)
12 );
13
14 inventory.forEach(item -> System.out.println("Checking " + item.sku()));
15 }
16}Output:
Checking SKU-1
Checking SKU-2
Checking SKU-3
Both versions visit the same three items in the same order. The second version has nothing left to get wrong about how the iteration itself is written.
Syntax
Iterable.forEach() runs directly on a collection, Stream.forEach() ends a pipeline, and forEachOrdered() protects the visiting order on a parallel stream.
1// File: ForEachSyntaxForms.java
2import java.util.*;
3import java.util.stream.*;
4
5public class ForEachSyntaxForms {
6 public static void main(String[] args) {
7 List<String> skus = List.of("SKU-1", "SKU-2", "SKU-3");
8
9 // Iterable.forEach() - runs directly on the collection, no stream needed
10 skus.forEach(sku -> System.out.println("Iterable forEach: " + sku));
11
12 // Stream.forEach() - a terminal operation ending a pipeline
13 skus.stream()
14 .filter(sku -> !sku.equals("SKU-2"))
15 .forEach(sku -> System.out.println("Stream forEach: " + sku));
16
17 // forEachOrdered() guarantees encounter order even on a parallel stream
18 skus.parallelStream()
19 .forEachOrdered(sku -> System.out.println("Ordered forEach: " + sku));
20 }
21}Output:
Iterable forEach: SKU-1
Iterable forEach: SKU-2
Iterable forEach: SKU-3
Stream forEach: SKU-1
Stream forEach: SKU-3
Ordered forEach: SKU-1
Ordered forEach: SKU-2
Ordered forEach: SKU-3
Common Use Cases
Choosing Between Iterable and Stream forEach
When no filtering or transformation is needed first, Iterable.forEach() skips the extra .stream() call entirely. Once a pipeline already exists, Stream.forEach() is simply how it ends.
1// File: IterableVsStreamForEachExample.java
2import java.util.*;
3import java.util.stream.*;
4
5public class IterableVsStreamForEachExample {
6 public static void main(String[] args) {
7 List<Integer> quantities = List.of(5, 40, 2, 15);
8
9 // No filtering or transformation needed - Iterable.forEach() is enough
10 quantities.forEach(quantity -> System.out.println("Quantity: " + quantity));
11
12 // A pipeline is already being built - Stream.forEach() ends it naturally
13 quantities.stream()
14 .filter(quantity -> quantity < 10)
15 .forEach(quantity -> System.out.println("Low stock: " + quantity));
16 }
17}Output:
Quantity: 5
Quantity: 40
Quantity: 2
Quantity: 15
Low stock: 5
Low stock: 2
Running Two Independent Actions Per Element
Consumer.andThen() combines two unrelated actions into a single Consumer, letting forEach() run both for every element without writing them inline as one tangled lambda.
1// File: ForEachWithAndThenExample.java
2import java.util.*;
3import java.util.function.*;
4
5public class ForEachWithAndThenExample {
6 public static void main(String[] args) {
7 List<String> skus = List.of("SKU-1", "SKU-2", "SKU-3");
8 List<String> processedLog = new ArrayList<>();
9
10 Consumer<String> printSku = sku -> System.out.println("Processing " + sku);
11 Consumer<String> logSku = sku -> processedLog.add(sku);
12
13 skus.forEach(printSku.andThen(logSku));
14
15 System.out.println("Log: " + processedLog);
16 }
17}Output:
Processing SKU-1
Processing SKU-2
Processing SKU-3
Log: [SKU-1, SKU-2, SKU-3]
Using a Method Reference to an Existing Action
The action passed to forEach() does not need to be written inline — a method reference to an already-existing method works exactly the same way.
1// File: ForEachMethodReferenceExample.java
2import java.util.*;
3
4public class ForEachMethodReferenceExample {
5 static void sendLowStockAlert(String sku) {
6 System.out.println("ALERT: " + sku + " is running low");
7 }
8
9 public static void main(String[] args) {
10 List<String> lowStockSkus = List.of("SKU-1", "SKU-3");
11
12 lowStockSkus.forEach(ForEachMethodReferenceExample::sendLowStockAlert);
13 }
14}Output:
ALERT: SKU-1 is running low
ALERT: SKU-3 is running low
Calling an Existing Method on Each Element
forEach() works well for triggering a method that mutates the element itself, which is a different, safer pattern than mutating shared state from outside the lambda.
1// File: ForEachMutatingElementsExample.java
2import java.util.*;
3
4public class ForEachMutatingElementsExample {
5 static class StockItem {
6 private final String sku;
7 private boolean checked = false;
8
9 StockItem(String sku) {
10 this.sku = sku;
11 }
12
13 void markChecked() {
14 checked = true;
15 }
16
17 @Override
18 public String toString() {
19 return sku + "(checked=" + checked + ")";
20 }
21 }
22
23 public static void main(String[] args) {
24 List<StockItem> items = List.of(new StockItem("SKU-1"), new StockItem("SKU-2"));
25
26 items.forEach(StockItem::markChecked);
27
28 System.out.println(items);
29 }
30}Output:
[SKU-1(checked=true), SKU-2(checked=true)]
Real-World Example
A warehouse management system's nightly job checks every stock item and, for the ones below their reorder threshold, needs to both print a console alert and record that alert in an audit log — two separate actions triggered by the same qualifying items. Filtering first to find the low-stock items, then running a combined Consumer through forEach(), keeps both actions clearly defined without mixing filtering logic and alerting logic into one tangled step.
1// File: StockItem.java
2
3public record StockItem(String sku, int quantity, int reorderThreshold) {}1// File: AlertService.java
2import java.util.*;
3import java.util.function.*;
4
5public class AlertService {
6 private final List<String> alertLog = new ArrayList<>();
7
8 public void broadcastLowStock(List<StockItem> inventory) {
9 Consumer<StockItem> printAlert = item ->
10 System.out.println("ALERT: " + item.sku() + " has only " + item.quantity() + " units left");
11 Consumer<StockItem> recordAlert = item -> alertLog.add(item.sku());
12
13 inventory.stream()
14 .filter(item -> item.quantity() < item.reorderThreshold())
15 .forEach(printAlert.andThen(recordAlert));
16 }
17
18 public List<String> getAlertLog() {
19 return alertLog;
20 }
21}1// File: WarehouseAlertDemo.java
2import java.util.*;
3
4public class WarehouseAlertDemo {
5 public static void main(String[] args) {
6 List<StockItem> inventory = List.of(
7 new StockItem("SKU-1", 5, 10),
8 new StockItem("SKU-2", 40, 10),
9 new StockItem("SKU-3", 2, 5),
10 new StockItem("SKU-4", 30, 20)
11 );
12
13 AlertService alertService = new AlertService();
14 alertService.broadcastLowStock(inventory);
15
16 System.out.println("Alert log: " + alertService.getAlertLog());
17 }
18}Output:
ALERT: SKU-1 has only 5 units left
ALERT: SKU-3 has only 2 units left
Alert log: [SKU-1, SKU-3]
A mistake that appears often in fresher pull requests is trying to stop processing the rest of the inventory the moment a critical stock-out is found, usually by reaching for a boolean flag checked at the top of the forEach() lambda to fake an early exit. forEach() has no way to stop partway through — every element still gets visited regardless of the flag — and code trying to simulate a break is almost always a sign that a plain for-loop, or a short-circuiting operation like anyMatch, was the right tool from the start.
Combining forEach() With Other Features
forEach() always takes a Consumer<T>, so andThen() from the Consumer article composes multiple independent actions into a single call exactly as AlertService does above. Iterable.forEach() and Stream.forEach() both exist because Iterable predates the Stream API by several versions — the default method was retrofitted onto Iterable in Java 8 specifically so every existing collection gained a lambda-friendly way to iterate without needing .stream() first. forEachOrdered() exists purely for the case where a parallel stream's usual lack of ordering guarantee would otherwise make output unpredictable.
Best Practices
Reach for Iterable.forEach() directly on a collection when no filtering or transformation is needed beforehand, and save Stream.forEach() for when it is genuinely ending a pipeline that already exists.
Treat mutating the element itself through its own method, like item.markChecked(), as a legitimate use of forEach(). Mutating shared external state from inside the lambda instead is the pattern worth avoiding, especially the moment a parallel stream enters the picture and multiple threads could touch that shared state at once.
Never try to simulate a break or an early return inside forEach(). If the logic genuinely needs to stop partway through, a plain for-loop or a short-circuiting terminal operation like anyMatch or findFirst is the correct tool, not a workaround built on a flag variable.
Combine multiple independent actions with Consumer.andThen() rather than writing one large lambda that tries to do several unrelated things inside a single forEach() call.
Common Mistakes
Trying to break out of forEach() early does not work the way a for-loop's break does — every element is still visited, even after the condition that was supposed to "stop" processing has already been satisfied.
1// File: ForEachNoBreakMistake.java
2import java.util.*;
3
4public class ForEachNoBreakMistake {
5 public static void main(String[] args) {
6 List<Integer> quantities = List.of(50, 30, 2, 80, 10);
7
8 // break; would not even compile inside this lambda - forEach has no
9 // way to stop iterating partway through
10
11 boolean[] stockOutFound = {false};
12 quantities.forEach(quantity -> {
13 if (quantity < 5 && !stockOutFound[0]) {
14 System.out.println("Stock-out found: " + quantity);
15 stockOutFound[0] = true;
16 }
17 // every remaining element is still visited, even after the flag is set
18 });
19
20 System.out.println("forEach always visits every element - a for-loop would have stopped early");
21 }
22}Output:
Stock-out found: 2
forEach always visits every element - a for-loop would have stopped early
Trying to chain another operation after forEach() does not compile, because forEach() returns void — it always has to be the last call in a pipeline, never a step in the middle of one.
1// File: ChainingAfterForEachMistake.java
2import java.util.*;
3
4public class ChainingAfterForEachMistake {
5 public static void main(String[] args) {
6 List<String> skus = List.of("SKU-1", "SKU-2");
7
8 // skus.stream()
9 // .forEach(sku -> System.out.println(sku))
10 // .filter(sku -> true);
11 // This does not compile - forEach() returns void, so nothing can
12 // be chained onto it; it must always be the LAST call in a pipeline
13
14 skus.stream()
15 .filter(sku -> sku.equals("SKU-1"))
16 .forEach(sku -> System.out.println("Only " + sku + " printed"));
17 }
18}Output:
Only SKU-1 printed
Relying on Stream.forEach() to process elements in encounter order becomes unsafe the moment the stream is parallel. forEach() explicitly makes no ordering guarantee on a parallel stream, and code that depends on one anyway will appear to work in casual testing and then produce a different visiting order under real load. forEachOrdered(), shown earlier in this article, is the method that actually guarantees order regardless of how the underlying work was parallelized.
Interview Questions
Q1. What does forEach() do, and what is the difference between Iterable.forEach() and Stream.forEach()?
Both run a given Consumer action once for every element, but Iterable.forEach() is a default method available directly on collections like List and Set since Java 8, while Stream.forEach() is a terminal operation that ends a stream pipeline. Iterable.forEach() needs no .stream() call first; Stream.forEach() only makes sense once a stream already exists, typically after other intermediate operations like filter or map.
Q2. Can you break out of a forEach() loop early?
No. forEach() always visits every element in the source, and there is no break-equivalent mechanism available inside the lambda passed to it. Simulating an early stop with a flag variable, as shown in this article's common mistakes, still processes every remaining element — it just skips running the action for them. If the logic genuinely needs to stop as soon as a condition is met, a plain for-loop or a short-circuiting operation like anyMatch or findFirst is the correct choice instead.
Q3. Does Stream.forEach() guarantee the order elements are processed in?
Only for a sequential stream with a defined encounter order. For a parallel stream, forEach() explicitly makes no ordering guarantee at all — elements may be processed by different threads in an order that has nothing to do with how they appeared in the source. This distinction is a frequent product-based interview question, since assuming order is preserved on a parallel stream is a genuinely common source of subtle bugs.
Q4. What is forEachOrdered(), and when would you use it instead of forEach()?
forEachOrdered() guarantees the action runs in the stream's encounter order, even when the stream is parallel, by forcing the results to be reassembled in order before the action runs. Use it whenever a parallel stream's ordering matters for correctness — printing a report in a specific sequence, for example — accepting that doing so gives up some of the performance benefit parallelism would otherwise provide.
Q5. Is forEach() an intermediate or terminal operation on a Stream?
Terminal. Stream.forEach() returns void, consumes the entire stream, and nothing can be chained after it — a stream that has already had forEach() called on it cannot be reused for a second operation, the same restriction that applies to every terminal operation.
Q6. Why is mutating shared external state inside forEach() considered risky, especially with parallel streams?
Because forEach() provides no synchronization of its own, and a parallel stream may run its action on multiple threads at the same time. A Consumer that mutates a shared, non-thread-safe collection or counter from inside forEach() can produce lost updates or corrupted state under parallel execution, even though the exact same code appears to work correctly every time on a sequential stream — which is exactly why it tends to pass casual testing and only fail once real concurrency is involved.
FAQs
Can forEach() return a value?
No. Both Iterable.forEach() and Stream.forEach() are declared to return void, since their entire purpose is running a side effect for each element rather than producing a result. If a result is actually needed, map() combined with collect(), or reduce(), is the correct operation instead.
Does forEach() work on a Map directly?
Yes, through Map.forEach(BiConsumer<? super K, ? super V> action), which hands each entry's key and value to the action as two separate arguments rather than requiring a Map.Entry to be unpacked manually.
Is there a performance difference between a for-loop and forEach()?
For most everyday code, the difference is negligible — both ultimately iterate the same elements, and the JIT compiler optimizes simple lambdas well. forEach()'s main advantage is readability, not raw speed, and a plain for-loop can still be the better choice when early termination or index-based access is genuinely needed.
Can forEach() throw a checked exception?
Only if the checked exception is caught and handled inside the Consumer passed to it. Consumer.accept() declares no checked exceptions, so a lambda used with forEach() cannot let one propagate out directly — it has to be wrapped as an unchecked exception or handled with a try-catch inside the lambda body.
Does calling forEach() twice on the same stream work?
No. Once Stream.forEach() has run as the terminal operation, that stream instance is fully consumed, and calling any operation on it again throws IllegalStateException. Iterable.forEach() has no such restriction, since it runs directly against a collection that can still be iterated as many times as needed afterward.
Is Iterable.forEach() available on arrays?
No. Arrays do not implement Iterable in Java, so array.forEach(...) is not valid syntax. Iterating an array with a Consumer-style action requires converting it to a stream first with Arrays.stream(array).forEach(...).
Can forEach() be used with an index, like a traditional for-loop?
Not directly — Consumer<T> only receives the element itself, with no index parameter available. When an index is genuinely needed alongside each element, IntStream.range(0, list.size()).forEach(i -> ...) is a common workaround, though a plain indexed for-loop is often clearer for that specific case.
Summary
forEach() is the operation for running an action against every element and nothing more — it takes a Consumer, visits everything, and returns void, which means it always sits at the very end of whatever pipeline it appears in. Iterable.forEach() and Stream.forEach() do the same conceptual job from two different starting points, and forEachOrdered() exists purely to keep that job predictable once parallelism is involved.
The two habits worth keeping are treating forEach() as genuinely un-interruptible — reach for a for-loop or a short-circuiting operation the moment early termination actually matters — and keeping its side effects limited to the element being visited rather than shared state sitting outside the lambda, exactly the distinction the warehouse alert example and the mutation use case both draw. reduce() and collect() are the operations to reach for next, whenever the goal shifts from "do something for each element" to "build a result out of all of them."