GroupMapCollate { public static <T, K, R> CompletableFuture<List<R>> groupMapCollate( List<T> items, Function<T, K> grouper, Function<K, Mapper<T, R>> mappers ) { List<Pending<T, R>> pending = new ArrayList<>(); for (int i = 0; i < items.size(); i++) { pending.add(new Pending<>(items.get(i), i)); } Map<K, List<Pending<T, R>>> grouped = pending.stream() .collect(Collectors.groupingBy(p -> grouper.apply(p.item))); Stream<CompletableFuture<Void>> futures = grouped.entrySet().stream().map(entry -> { K key = entry.getKey(); List<Pending<T, R>> pendingForKey = entry.getValue(); Mapper<T, R> mapper = mappers.apply(key); CompletableFuture<List<R>> future = mapper.map( pendingForKey.stream() .map(p -> p.item) .collect(Collectors.toList()) ); return future.thenAccept(results -> { if (results.size() != pendingForKey.size()) { String message = String.format( "result size (%s) does not match pending size (%s)", results.size(), pendingForKey.size()); LoggerFactory.getLogger(GroupMapCollate.class).error(message); throw new RuntimeException(message); } for (int i = 0; i < pendingForKey.size(); i++) { pendingForKey.get(i).result = results.get(i); } }); }); return allOf(futures).thenApply( v -> pending.stream() .map(p -> p.result) .collect(Collectors.toList()) ); }  static CompletableFuture<List<R>> groupMapCollate(
        List<T> items,
        Function<T, K> grouper,
        Function<K, Mapper<T, R>> mappers
    );  }
@Test public void testGroupMapCollate() throws ExecutionException, InterruptedException { int N = 10; List<Integer> items = new ArrayList<>(); IntStream.range(0, N).forEach(items::add); for (int i = 1; i <= N; i++) { final int mod = i; CompletableFuture<List<String>> stringsFuture = groupMapCollate( items, item -> item % mod, remainder -> group -> { System.out.println("mod=" + mod + " remainder=" + remainder + " group=" + group); CompletableFuture<List<String>> future = new CompletableFuture<>(); future.complete(group.stream().map(Object::toString).collect(toList())); return future; } ); List<String> strings = stringsFuture.get(); for (int j = 0; j < strings.size(); j++) { assertEquals(String.valueOf(j), strings.get(j)); } System.out.println("--"); } }