Sitelet https://howtodoinjava.com/java8/java-stream-foreach/

Java Stream forEach(): Syntax, Examples and Pitfalls

Stream.forEach() runs an action on every element of a stream as the last step of the pipeline. See its rules, parallel order, safe side effects and how to stop early.

Java Stream forEach()

The Java Stream forEach(action) method is a terminal operation that runs a Consumer action once for every element of a stream and returns nothing. The action is a lambda or a method reference, and forEach() is the last call of the stream pipeline, so the stream cannot be used again after it.

We use forEach() at the end of a pipeline to do something with each result, such as print it or send it to another system. For example, a music app filters a playlist and shows each song title that matches the search.

The following example calls forEach() on a playlist of four songs in different ways, with the printed result of each line as a comment.

List<String> songs = List.of("Yesterday", "Help", "Hey Jude", "Let It Be");

songs.stream().forEach(System.out::println);                                  // Yesterday, Help, Hey Jude, Let It Be
songs.stream().filter(s -> s.length() > 8).forEach(System.out::println);      // Yesterday, Let It Be
songs.stream().map(String::toUpperCase).forEach(System.out::println);         // YESTERDAY, HELP, HEY JUDE, LET IT BE
songs.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println); // Yesterday, Let It Be, Hey Jude, Help

songs.parallelStream().forEach(System.out::println);          // any order, e.g. Hey Jude, Let It Be, Help, Yesterday
songs.parallelStream().forEachOrdered(System.out::println);   // Yesterday, Help, Hey Jude, Let It Be

Notice the last two lines. On a parallel stream, forEach() prints the songs in whatever order the threads finish, whereas forEachOrdered() keeps the order of the list.

Next, we look at the method signature and the rules of forEach(), then at examples with arrays and maps. After that, we cover parallel streams and shared state, how to stop early, checked exceptions in the action, and when a plain for loop is the better choice.

1. Stream forEach() Syntax

The method is declared in the interface Stream and takes one argument.

void forEach(Consumer<? super T> action)

The parameter is a Consumer, a functional interface whose method accept(T) takes one element and returns no result. So the argument can take one of these forms.

  • A lambda such as s -> System.out.println(s).
  • A method reference such as System.out::println.
  • A Consumer variable, which lets us reuse the same action in several pipelines.
Consumer<String> printSong = song -> System.out.println("Now playing: " + song);
songs.stream().forEach(printSong);    // Now playing: Yesterday, Now playing: Help, ...

1.1. Rules of forEach()

A few rules apply to Stream.forEach(), and they matter as soon as the stream is parallel or the action changes some state.

  • The method is a terminal operation. It runs the whole pipeline, and after it, the stream is consumed.
  • The method returns void, so we cannot chain another stream method after it.
  • The order is not guaranteed. The Javadoc calls the behavior “explicitly nondeterministic”, and on a parallel stream the elements arrive in any order.
  • The action may run in any thread. If the action changes shared state, the action must synchronize the access itself.
  • The action must be non-interfering, i.e. it must not change the source collection of the stream.

A consumed stream throws an IllegalStateException when we call forEach() on it a second time. For a second pass, we create a new stream from the source, for example with another call to songs.stream(). The stream reuse error covers this exception in detail.

Stream<String> stream = songs.stream();
stream.forEach(s -> { });
stream.forEach(System.out::println);    // IllegalStateException: stream has already been operated upon or closed

2. Stream forEach() Examples

In a sequential stream from a List, forEach() processes the elements in list order. The examples in this section use sequential streams, and all of them run on Java 25 (the code compiles on Java 21 or later).

2.1. Arrays and Maps

An array has no stream() method, so we create the stream with Arrays.stream() or Stream.of(). A Map has no stream() method either, so we stream its entrySet(), which gives one Map.Entry per key-value pair.

String[] artists = {"Beatles", "Lennon"};
Arrays.stream(artists).forEach(System.out::println);       // Beatles, Lennon

Map<String, Integer> plays = new LinkedHashMap<>();
plays.put("Yesterday", 12);
plays.put("Help", 30);

plays.entrySet().stream()
    .filter(e -> e.getValue() > 20)
    .forEach(e -> System.out.println(e.getKey() + " = " + e.getValue()));   // Help = 30

When we do not need a stream operation such as filter(), Map.forEach() with a two-argument lambda is shorter. The forEach() methods of Iterable and Map work without a stream.

plays.forEach((song, count) -> System.out.println(song + " -> " + count));   // Yesterday -> 12, Help -> 30

2.2. Reverse Order

The method forEach() has no order setting of its own. To process the elements in reverse order, we sort the stream before forEach(), and for a List in Java 21 or later, we can stream reversed(), which reverses the list order instead of sorting.

songs.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);   // Yesterday, Let It Be, Hey Jude, Help
songs.reversed().stream().forEach(System.out::println);                          // Let It Be, Hey Jude, Help, Yesterday

3. forEach() on Parallel Streams

A parallel stream splits the elements into parts and processes the parts on several threads of the common ForkJoinPool. The method forEach() runs the action as soon as a thread reaches an element, so the order of the output depends on the thread timing. That is the point of parallelism, because no thread waits for another one.

When the order matters, we call forEachOrdered(), which runs the action in the encounter order of the stream (the order of the source list). The threads still compute the earlier pipeline steps in parallel, but forEachOrdered() delivers the elements one after another.

List<String> order = Collections.synchronizedList(new ArrayList<>());
songs.parallelStream().forEach(order::add);             // [Hey Jude, Let It Be, Help, Yesterday]

List<String> ordered = new ArrayList<>();
songs.parallelStream().forEachOrdered(ordered::add);    // [Yesterday, Help, Hey Jude, Let It Be]
songs.parallelStream() with Yesterday, Help, Hey Jude and Let It Be is split into parts that threads of the common ForkJoinPool process at the same time. forEach() runs each element when a thread reaches it, for example Hey Jude, Let It Be, Help, Yesterday, and the order depends on thread timing. forEachOrdered() runs the elements in encounter order, Yesterday, Help, Hey Jude, Let It Be, on every run.
On a parallel stream, forEach() prints the elements as the threads reach them, whereas forEachOrdered() keeps the order of the source.

3.1. Shared State in the Action

The action of a parallel forEach() runs on several threads at the same time, so it must not write to a collection that is not thread-safe. An ArrayList is not thread-safe, so when two threads call add() together, one element can overwrite the other. We added 100,000 numbers to an ArrayList from a parallel forEach() in five runs.

List<Integer> unsafe = new ArrayList<>();
IntStream.range(0, 100_000).parallel().boxed().forEach(unsafe::add);
int size = unsafe.size();          // 95076, 100000, 75163, 75000, 100000 in five runs

List<Integer> safe = IntStream.range(0, 100_000).parallel().boxed().toList();
int safeSize = safe.size();        // 100000

Three of the five runs lost elements, and none of them threw an exception, so the bug can stay hidden until production. The safe version lets the stream build the list with toList(), which works correctly on parallel streams.

4. Use forEach() Only for Side Effects

A side effect is anything the action does outside the stream, such as printing or writing to a list. The method forEach() exists only for side effects, because it returns nothing. When the goal is a new collection, a terminal operation such as toList() or collect() is the right tool, and it is also safe on parallel streams.

List<String> longTitles = new ArrayList<>();
songs.stream().filter(s -> s.length() > 8).forEach(longTitles::add);    // works, but only on a sequential stream

List<String> titles = songs.stream().filter(s -> s.length() > 8).toList();   // [Yesterday, Let It Be]

The action must also leave the source of the stream unchanged. For example, a playlist app adds a live version of each song to the same queue that it streams. The ArrayList detects the change and throws a ConcurrentModificationException, and by then the action has already added some elements.

List<String> queue = new ArrayList<>(songs);
queue.stream().forEach(s -> queue.add(s + " (live)"));   // ConcurrentModificationException, queue.size() = 8

List<String> queue2 = new ArrayList<>(songs);
List<String> live = queue2.stream().map(s -> s + " (live)").toList();
queue2.addAll(live);                                      // [Yesterday, ..., Let It Be (live)], size 8

The safe version builds the new elements first and adds them to the list after the stream has finished.

5. Stopping forEach() Early

The method forEach() has no break or continue, because the action is a lambda and not a loop body. A return inside the action ends only the call for the current element, so it works like continue, and the stream goes on with the next element.

songs.stream().forEach(s -> {
  if (s.startsWith("H")) {
    return;                      // skips Help and Hey Jude
  }
  System.out.println(s);         // Yesterday, Let It Be
});

To stop at the first element that fails a condition, we call takeWhile() (Java 9+) before forEach(). To check whether any element matches, we call a short-circuiting operation such as anyMatch() or findFirst(), which stop at the first match.

songs.stream().takeWhile(s -> !s.startsWith("H")).forEach(System.out::println);   // Yesterday
boolean hasHelp = songs.stream().anyMatch(s -> s.equals("Help"));                 // true

If the loop body needs break together with other logic, a for-each loop is clearer than any stream workaround.

6. Checked Exceptions Inside forEach()

The method Consumer.accept() declares no checked exceptions, so the action cannot throw an IOException or another checked exception. A call such as out.write(song) does not compile inside the lambda without a try-catch.

For example, an export feature writes each song of a playlist to a file. The action catches the IOException and wraps it in an UncheckedIOException, which forEach() passes on to the caller, so the export stops at the first failed write instead of skipping it.

try (Writer out = Files.newBufferedWriter(Path.of("playlist.txt"))) {
  songs.stream().forEach(song -> {
    try {
      out.write(song + "\n");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  });
} catch (IOException | UncheckedIOException e) {
  System.out.println("Export failed: " + e.getMessage());
}
// playlist.txt: Yesterday, Help, Hey Jude, Let It Be (one per line)

When the method that contains the loop already declares throws IOException, a for-each loop needs no wrapping at all. The guide to exceptions in streams shows more patterns, such as a wrapper method for any throwing lambda.

7. forEach() vs the for-each Loop

The for-each loop is an external iterator, because our code asks the collection for each element. The method forEach() is an internal iterator, because the stream controls the iteration and calls our action for each element. That difference explains every row of the table.

Stream forEach()for-each loop
Iterationinternal, the stream calls the actionexternal, our code pulls each element
break and continueno, return skips one elementyes
Checked exceptionsmust be wrappedallowed when the method declares them
Local variablesonly effectively final ones can be read, none can be changedcan be read and changed
Parallel executionyes, with parallelStream()no
Fits bestthe last step of a stream pipelineloops with control flow or checked exceptions

We use forEach() when the elements come out of a pipeline with filter(), map() or sorted(), and the action is short. We use a for-each loop when we iterate a collection without stream operations and need break, a counter or checked exceptions.

8. Stream forEach() FAQs

8.1. What Is the Difference Between list.stream().forEach() and list.forEach()?

The method list.forEach() comes from Iterable and iterates the list without a stream. Its Javadoc guarantees the iteration order of the list, whereas Stream.forEach() gives no order guarantee. So when we only want to visit every element of a collection, list.forEach() is shorter and keeps the order. We add stream() only when the loop needs stream operations such as filter() or map().

8.2. How Do I Get the Index in forEach()?

The method forEach() passes only the element, not its index. For a List, we stream the indexes with IntStream.range() and read each element with get(i). More options are in iterating a stream with indices.

IntStream.range(0, songs.size())
    .forEach(i -> System.out.println((i + 1) + ". " + songs.get(i)));   // 1. Yesterday, 2. Help, 3. Hey Jude, 4. Let It Be

8.3. Can forEach() Return a Value?

No. The method forEach() returns void, and the action cannot return a value to the stream either. To transform the elements, we call map() and end the pipeline with toList() or collect(). To look at the elements in the middle of a pipeline, for example for logging, we call peek().

9. Conclusion

Stream.forEach() runs a Consumer action on every element of a stream as the last step of the pipeline. It returns nothing and consumes the stream, so a second pass needs a new stream from the source.

On a sequential stream from a list, forEach() processes the elements in list order, but the Javadoc gives no order guarantee, and on a parallel stream the order changes from run to run. When the order matters, we call forEachOrdered(). The action must not write to a collection that is not thread-safe, and it must not change the source of the stream.

We keep forEach() for side effects such as printing and logging, and build new collections with toList() or collect(). For loops that need break or checked exceptions, a for-each loop is the clearer choice.

10. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.