Lambda to obtain a new merged object The requirement is to iterate over a list of Foos, and when 2 Foos have the same id merge their list of inputs together and obtain a new object.

I have achieved this with 2 streams and a private method to do this:

import java.util.HashSet;
import java.util.Set;

class Foo {
    private String id;
    private Set&lt;String&gt; inputs = new HashSet&lt;&gt;();

    String getId() {
        return id;
    }

    void setId(String id) {

        this.id = id;
    }

    Set&lt;String&gt; getInputs() {
        return inputs;

    }
}


and the GetMatchingFoos class:

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;

import java.util.List;
import java.util.Map;

public class GetMatchingFoos {

    public List&lt;Foo&gt; call(List&lt;Foo&gt; matchedFoos) {
        Map&lt;String, List&lt;Foo&gt;&gt; idToMatchedFooMap = matchedFoos.stream()
                .collect(groupingBy(Foo::getId, toList()));

        List&lt;Foo&gt; mergedAndMatchedFoos = idToMatchedFooMap.values().stream()
                .map(this::mergeFoos)
                .collect(toList());

        return mergedAndMatchedFoos;
    }

    private Foo mergeFoos(List&lt;Foo&gt; matchedFoos) {
        Foo result = new Foo();
        for(Foo matchedFoo : matchedFoos) {
            result.setId(matchedFoo.getId());
            result.getInputs().addAll(matchedFoo.getInputs());
        }
        return result;
    }

}


Can I obtain the same result by using only a single lambda operation that is more elegant than the above? I have looked into reducing but it was not very trivial.
To determine the result you're after, it is necessary to group by the id and hold a temporary map of the grouping operation.

You can make it a bit simpler, and not have a second Stream pipeline, by using the collectingAndThen built-in collector, which gives the ability to pass a finisher operation to the result of another collector.

Collection&lt;Foo&gt; result = 
    matchedFoos.stream()
               .collect(groupingBy(Foo::getId, collectingAndThen(toList(), this::mergeFoos)))
               .values();


This code still collects all the foos having the same id into a list, but the use of collectingAndThen enables us to directly pass that list to mergeFoos and return the wanted Foo. Then, it is possible to retrieve the values by invoking values().

This will return a Collection&lt;Foo&gt; as values() is defined to return that. If you really want a List, you can convert it easily to an ArrayList for example, with:

List&lt;Foo&gt; mergedAndMatchedFoos = new ArrayList&lt;&gt;(result);




Note that the current code stores every foo having the same id in a list. This is a bit inefficient since, for the end result, we're interested in a single Foo where the inputs are all input of the foos in the list. So, instead, we can use the toMap(keyMapper, valueMapper, mergeFunction) collector and directly build the intermediate Map&lt;String, Foo&gt; instead a Map&lt;String, List&lt;Foo&gt;&gt;.

The key mapper returns the id of the foo; the value mapper returns a new Foo from a given one, and the merge function adds all inputs from one foo to the other.

Map&lt;String, Foo&gt; result =
    matchedFoos.stream()
       .collect(Collectors.toMap(
          Foo::getId,
          Foo::new,
          (foo1, foo2) -&gt; { foo1.getInputs().addAll(foo2.getInputs()); return foo1; }
       ));

List&lt;Foo&gt; mergedAndMatchedFoos = new ArrayList&lt;&gt;(result.values());


This assumes that we have a copy-constructor Foo(Foo foo) constructing a Foo from another Foo (which is what the Foo::new method-reference refers to). The mutation of foo1 inside the merge function is safe to do since we're working on the new instance, constructed with the copy constructor.