It's still pretty ugly, and I think it would improve by changing the way you model things a bit, but I still managed to find the following:
public static void main(String[] args) { Person[] people = {new Person("00001"), new Person("00002"), new Person("00005")}; Region[] regions = { new Region("Region 1", Arrays.asList("00001", "00002", "00003")), new Region("Region 2", Arrays.asList("00002", "00003", "00004")), new Region("Region 3", Arrays.asList("00001", "00002", "00005")) }; Map<Person, List<Region>> result = Stream.of(regions) .flatMap(region -> region.getZipCodes().stream() .map(zip -> new SimpleEntry<>(zip, region))) .flatMap(entry -> Stream.of(people) .filter(person -> person.getZip().equals(entry.getKey())) .map(person -> new SimpleEntry<>(person, entry.getValue()))) .collect(Collectors.groupingBy(Entry::getKey, Collectors.mapping(Entry::getValue, Collectors.toList()))); result.entrySet().forEach(entry -> System.out.printf("[%s]: {%s}\n", entry.getKey(), entry.getValue()));
Having a ZipCode class that contains a mapping and can be put into action will make things cleaner:
public static void main(String[] args) { Region r1 = new Region("Region 1"); Region r2 = new Region("Region 2"); Region r3 = new Region("Region 3"); ZipCode zipCode1 = new ZipCode("00001", Arrays.asList(r1, r3)); ZipCode zipCode2 = new ZipCode("00002", Arrays.asList(r1, r2, r3)); ZipCode zipCode3 = new ZipCode("00003", Arrays.asList()); ZipCode zipCode4 = new ZipCode("00004", Arrays.asList()); ZipCode zipCode5 = new ZipCode("00005", Arrays.asList(r3)); Person[] people = { new Person(zipCode1), new Person(zipCode2), new Person(zipCode5) }; Map<Person, List<Region>> result = Stream.of(people) .collect(Collectors.toMap(person -> person, person -> person.getZip().getRegions())); result.entrySet().forEach(entry -> System.out.printf("[%s]: {%s}\n", entry.getKey(), entry.getValue()));