The forEach is designed to be a terminal operation and yes - you can't do anything after you call it.

The idiomatic way would be to apply a transformation first and then collect() everything to the desired data structure.

The transformation can be performed using map which is designed for non-mutating operations.

If you are performing a non-mutating operation:

 items.stream()
   .filter(s -> s.contains("B"))
   .map(s -> s.withState("ok"))
   .collect(Collectors.toList());

where withState is a method that returns a copy of the original object including the provided change.


If you are performing a side effect:

items.stream()
  .filter(s -> s.contains("B"))
  .collect(Collectors.toList());

items.forEach(s -> s.setState("ok"))
Answer from Grzegorz Piwowarek on Stack Overflow
🌐
HowToDoInJava
howtodoinjava.com › home › java 8 › java collect stream to list (with examples)
Java Collect Stream to List (with Examples)
April 29, 2024 - To collect items in any other List types, we must use the Stream.collect(Collectors.toCollection(LinkedList::new)) version of the solutions. Happy Learning !! Sourcecode on Github · Subscribe · 1 Comment · Most Voted · Newest Oldest · Inline Feedbacks · View all comments · Load More Comments · Java 8 Features · Java 8 forEach ·
🌐
Baeldung
baeldung.com › home › java › java collections › the difference between collection.stream().foreach() and collection.foreach()
The Difference Between stream().forEach() and forEach() | Baeldung
September 17, 2025 - Java only requires all threads to finish before any terminal operation, such as Collectors.toList(), is called. Let’s look at an example where we first call forEach() directly on the collection, and second, on a parallel stream: ...
🌐
Javaprogramto
javaprogramto.com › 2020 › 12 › java-8-stream-foreach-collect.html
Java 8 Stream foreach Collect - Can not be done? JavaProgramTo.com
December 9, 2020 - So, we need to use another method map() rather than forEach() which will do modifying the values and returns a stream of new string values. And further we can collect the result into List and use forEach() method.
🌐
Stack Abuse
stackabuse.com › guide-to-java-streams-foreach-with-examples
Guide to Java Streams: forEach() with Examples
July 27, 2020 - AtomicInteger result2 = new AtomicInteger(); targetList.stream().forEach(integer -> { if (integer > 0) result2.addAndGet(1); }); System.out.println("Result: " + result2); The forEach() method is a really useful method to use to iterate over collections in Java in a functional approach. In certain cases, they can massively simplify the code and enhance clarity and brevity. In this article, we've gone over the basics of using a forEach() and then covered examples of the method on a List...
🌐
Stackify
stackify.com › streams-guide-java-8
A Guide to Java Streams: In-Depth Tutorial With Examples
September 4, 2024 - The strategy for this operation is provided via the Collector interface implementation. In the example above, we used the toList collector to collect all Stream elements into a List instance.
🌐
Tabnine
tabnine.com › home page › code › java › java.util.stream.stream
java.util.stream.Stream.forEach java code examples | Tabnine
private List<Symbol> getPushedDownGroupingSet(AggregationNode aggregation, Set<Symbol> availableSymbols, Set<Symbol> requiredJoinSymbols) { List<Symbol> groupingSet = aggregation.getGroupingKeys(); // keep symbols that are directly from the join's child (availableSymbols) List<Symbol> pushedDownGroupingSet = groupingSet.stream() .filter(availableSymbols::contains) .collect(Collectors.toList()); // add missing required join symbols to grouping set Set<Symbol> existingSymbols = new HashSet<>(pushedDownGroupingSet); requiredJoinSymbols.stream() .filter(existingSymbols::add) .forEach(pushedDownGroupingSet::add); return pushedDownGroupingSet; } origin: spring-projects/spring-framework ·
🌐
Medium
medium.com › @AlexanderObregon › javas-stream-collect-method-explained-956667984a8f
Java’s Stream.collect() Method Explained | Medium
November 28, 2024 - The partitioningBy collector takes a predicate and divides the elements of the stream into two groups: one group where the predicate evaluates to true and another where it evaluates to false. The result is a Map<Boolean, List<T>> structure. When working with numerical data, you can use summarizingInt, summarizingDouble, or summarizingLong to obtain statistical summaries. import java.util.IntSummaryStatistics; import java.util.stream.Collectors; import java.util.stream.Stream; public class SummarizingExample { public static void main(String[] args) { IntSummaryStatistics stats = Stream.of(5, 10, 15, 20) .collect(Collectors.summarizingInt(Integer::intValue)); System.out.println("Count: " + stats.getCount()); // Output: Count: 4 System.out.println("Sum: " + stats.getSum()); // Output: Sum: 50 System.out.println("Average: " + stats.getAverage()); // Output: Average: 12.5 } }
Find elsewhere
🌐
Oracle
docs.oracle.com › javase › 8 › docs › api › java › util › stream › Stream.html
Stream (Java Platform SE 8 )
2 weeks ago - If the stream is parallel, and the Collector is concurrent, and either the stream is unordered or the collector is unordered, then a concurrent reduction will be performed (see Collector for details on concurrent reduction.) This is a terminal operation. When executed in parallel, multiple intermediate results may be instantiated, populated, and merged so as to maintain isolation of mutable data structures. Therefore, even when executed in parallel with non-thread-safe data structures (such as ArrayList), no additional synchronization is needed for a parallel reduction. ... Map<String, List<Person>> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity));
🌐
JRebel
jrebel.com › blog › java-streams-in-java-8
Using Java Streams in Java 8 and Beyond | JRebel by Perforce
Collect: This is the way to get out of the streams world and obtain a concrete collection of values, like a list in the example above.Back to top · There are some caveats of using the Java streaming API though, and sometimes stream processing can get out of hand.
🌐
Medium
medium.com › @AlexanderObregon › javas-collectors-tolist-method-explained-0e90e5d90dcb
Java’s Collectors.toList() Method Explained | Medium
August 23, 2024 - import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class CollectorsToListExample { public static void main(String[] args) { List<String> collectedList = Stream.of("Red", "Green", "Blue") .collect(Collectors.toList()); collectedList.forEach(System.out::println); } }
🌐
Stack Abuse
stackabuse.com › java-8-streams-convert-a-stream-to-list
Guide to Java 8 Collectors: Definitive Guide to toList()
November 19, 2024 - In this guide, we'll jump into detail of how Stream collection into Lists works and how you can use pre-defined Collectors and define your own to convert a stream into a list in Java 8.
🌐
Medium
medium.com › @code.wizzard01 › java-streams-and-collectors-a-practical-guide-and-cheat-sheet-with-real-world-examples-67dcf84156b5
Java Streams and Collectors: A Practical Guide and Cheat Sheet with Real-World Examples | by Code Wiz | Medium
May 10, 2025 - Terminal Operations: Trigger the ... collect, forEach, reduce). These operations are eager and consume the stream. Once a terminal operation is called, the stream cannot be reused. Collectors: Special classes that define how to collect the results of a stream into a specific data structure (e.g., List, Set, ...
🌐
Mkyong
mkyong.com › home › java8 › java 8 – convert a stream to list
Java 8 - Convert a Stream to List - Mkyong.com
March 18, 2019 - package com.mkyong.java8; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class Java8Example2 { public static void main(String[] args) { Stream<Integer> number = Stream.of(1, 2, 3, 4, 5); List<Integer> result2 = number.filter(x -> x != 3).collect(Collectors.toList()); result2.forEach(x -> System.out.println(x)); } } output ·
🌐
GeeksforGeeks
geeksforgeeks.org › java › difference-between-collection-stream-foreach-and-collection-foreach-in-java
Difference Between Collection.stream().forEach() and Collection.forEach() in Java - GeeksforGeeks
February 2, 2021 - // Java Program to show the demonstration of // Collection.stream().forEach() import java.io.*; import java.util.*; class GFG { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(5); list.add(6); list.add(3); list.add(4); // printing each element of list using forEach loop list.stream().forEach(System.out::print); } } Output ·
🌐
YouTube
youtube.com › watch
Java Tutorial - Collection Looping with forEach() and stream().forEach() - YouTube
In this episode, I introduce you to the Java 8 forEach() method to easily loop through a Collection of elements in a simple and concise manner. I also give a...
Published   February 4, 2021
🌐
Java Guides
javaguides.net › 2020 › 04 › java-8-stream-filter-and-foreach-example.html
Java 8 Stream - filter() and forEach() Example
September 14, 2020 - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * Stream filter and forEach() method example * * @author Ramesh Fadatare * */ public class StreamFilterExample { public static void main(String[] args) { // using stream API List < Product > filteredProducts = getProducts().stream() .filter((product) -> product.getPrice() > 25000 f) .collect(Collectors.toList()); filteredProducts.forEach(System.out::println); } private static List < Product > getProducts() { List < Product > productsList = new ArrayList < Product > (); productsList.add(new Product(1, "HP Laptop", 25000 f)); productsList.add(new Product(2, "Dell Laptop", 30000 f)); productsList.add(new Product(3, "Lenevo Laptop", 28000 f)); productsList.add(new Product(4, "Sony Laptop", 28000 f)); productsList.add(new Product(5, "Apple Laptop", 90000 f)); return productsList; } }
Top answer
1 of 3
12

Instead of using a forEach just use streams from the beginning:

List<PersonWrapper> wrapperList = jrList.stream()
    .flatMap(jr -> seniorList.stream()
         .filter(sr -> jr.getName().equals(sr.getName()))
         .map(sr -> new PersonWrapper(jr, sr))
    )
    .collect(Collectors.toList());

By using flatMap you can flatten a stream of streams (Stream<Stream<PersonWrapper>>) into a single stream (Stream<PersonWrapper>)

If you can't instantiate wrapperList by yourself or really need to append to it. You can alter above snippet to following:

List<PersonWrapper> wrapperList = new ArrayList<>();

jrList.stream()
    .flatMap(jr -> seniorList.stream()
         .filter(sr -> jr.getName().equals(sr.getName()))
         .map(sr -> new PersonWrapper(jr, sr))
    )
    .forEach(wrapperList::add);
2 of 3
4

While Lino's answer is certainly correct. I would argue that if a given person object in jrList can only ever have one corresponding match in seniorList maximum, in other words, if it's a 1-1 relationship then you can improve upon the solution given by Lino by finding the first match as follows:

List<PersonWrapper> resultSet = jrList.stream()
                .map(p -> seniorList.stream()
                        .filter(sr -> p.getName().equals(sr.getName()))
                        .findFirst()
                        .map(q -> new PersonWrapper(p, q))
                        .get())
                .collect(Collectors.toList());

or if there is no guarantee that each person in jrList will have a corresponding match in seniorList then change the above query to:

List<PersonWrapper> resultSet = jrList.stream()
                .map(p -> seniorList.stream()
                        .filter(sr -> p.getName().equals(sr.getName()))
                        .findFirst()
                        .map(q -> new PersonWrapper(p, q))
                        .orElse(null))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

The difference is that now instead of calling get() on the result of findFirst() we provide a default with orElse in case findFirst cannot find the corresponding value and then we filter the null values out in the subsequent intermediate operation as they are not needed.