Using Java stream to filter a list and create a map In List&lt;Hotel&gt;, I'd like to extract Hotels which has OTAProperty with a specific otaName, as Map&lt;Long, Hotel&gt;. The map's key is the id for the OTAProperty named given otaName, and the value for the map is the Hotel instance.

I made a method createHotelMapByOtaName working as intended. But I think it's possible to remove nested forEach block by using Java 8's stream and related operations such as map, flatMap, and Collectors.toMap. Can anyone get some advice or code snippets?

package com.example;

import com.google.common.collect.Lists;

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

public class HotelService {

    public static Map&lt;Long, Hotel&gt; createHotelMapByOtaName(List&lt;Hotel&gt; hotels, String otaName) {
        Map&lt;Long, Hotel&gt; map = new HashMap&lt;&gt;();
        hotels.forEach(h -&gt; {
            h.getOtaProperties().forEach(op -&gt; {
                if (otaName.equals(op.getOtaName())) map.put(op.getId(), h);
            });
        });
        return map;
    }

    public static void main(String[] args) {
        List&lt;Hotel&gt; hotels = Lists.newArrayList(
                new Hotel("Hilton",
                        Lists.newArrayList(
                                new OTAProperty(1L, "expedia"),
                                new OTAProperty(2L, "agoda"))),
                new Hotel("Ramada",
                        Lists.newArrayList(
                                new OTAProperty(4L, "agoda"))),
                new Hotel("Hyatt",
                        Lists.newArrayList(
                                new OTAProperty(5L, "expedia")))
        );
        // expected: {1=Hotel{name=Hilton, otaProperties=[OTAProperty{id=1, otaName=expedia}, OTAProperty{id=2, otaName=agoda}]}, 5=Hotel{name=Hyatt, otaProperties=[OTAProperty{id=5, otaName=expedia}]}}
        System.out.println(HotelService.createHotelMapByOtaName(hotels, "expedia"));
    }
}


Followings are codes for 2 models:

Hotel.java:

package com.example;

import com.google.common.base.MoreObjects;

import java.util.List;

public class Hotel {
    private String name;
    private List&lt;OTAProperty&gt; otaProperties;

    public Hotel(String name, List&lt;OTAProperty&gt; otaProperties) {
        this.name = name;
        this.otaProperties = otaProperties;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List&lt;OTAProperty&gt; getOtaProperties() {
        return otaProperties;
    }

    public void setOtaProperties(List&lt;OTAProperty&gt; otaProperties) {
        this.otaProperties = otaProperties;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("name", name)
                .add("otaProperties", otaProperties)
                .toString();
    }
}


OTAProperty.java:

package com.example;

import com.google.common.base.MoreObjects;

public class OTAProperty {
    private Long id;
    private String otaName;

    public OTAProperty(Long id, String otaName) {
        this.id = id;
        this.otaName = otaName;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getOtaName() {
        return otaName;
    }

    public void setOtaName(String otaName) {
        this.otaName = otaName;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("id", id)
                .add("otaName", otaName)
                .toString();
    }
}
This transformation is not so obvious, but still feasible without instantiating a dedicated Map directly and without using forEach.

First of all, I suggest to implement hashCode and equals in Hotel and OTAProperty classes: this is often useful, especially when storing objects in collections.

Here is how the functional sequence can look like:

public static Map&lt;Long, Hotel&gt; createHotelMapByOtaName2(List&lt;Hotel&gt; hotels, String otaName) {
  return hotels.stream()
             .map(hotel -&gt;
                hotel.getOtaProperties().stream()
                     .filter(op -&gt; otaName.equals(op.getOtaName())) // 1
                     .collect(Collectors.toMap(OTAProperty::getId,  
                                               (h) -&gt; hotel))) // 2
             .map(Map::entrySet) // 3
             .flatMap(Collection::stream) // 4
             .collect(Collectors.toMap(Map.Entry::getKey,
                                       Map.Entry::getValue)); // 5
}



Equivalent of the original if condition: we take only OTAProperties that match the required name.
Each hotel item is wrapped to a Map&lt;Long, Hotel&gt;, which is a subset of the resulting map.
Transformation of the stream of Maps into a stream of Set&lt;Entry&lt;Long, Hotel&gt;&gt;.
Concatenation of streams of Sets into a single stream of Entry&lt;Long, Hotel&gt;. Unfortunately, we cannot join Maps so easily, that is why there is this intermediate transformation.
Finally, the stream of entries is collected into a Map representing the result of the sequence.