to answer this question, we can first see why the result of  Map < String, List < List < String >  appears. This depends on the design semantics of  Collectors.groupingBy . It represents a way to classify and group stream data according to certain rules, and to provide a method of how to collect the same group of data, so this is the meaning of  Collectors.groupingBy  two parameters 
.
 the first parameter of the subject is  Condition::getCondName , and the  Condition  representing the flow is grouped according to its  condName  attribute. After grouping, the  Condition  of the same group is handled. Here the main  Collectors.mapping (Condition::getCondValue, Collectors.toList () ,  mapping  represents the mapping transformation. Here it is somewhat similar to grouping.  Collectors.mapping  the first parameter sets the  Condition  of the stream. At this point, the stream is  List < String > , followed by the second parameter  Collectors.toList () , oh, of course, the final result is  List < List < String > >  
.
 the reason why the subject can't get the answer here is that the second parameter of  Collectors.mapping  is not written correctly. I can think of three ways of 
 the first: still use  Collectors.mapping , similar to the one mentioned by the subject, go over it again,  
.
Map<String, List<String>> collect = conditions.stream()
                .collect(Collectors.groupingBy(Condition::getCondName,
                                Collectors.mapping(Condition::getCondValue,
                                        Collectors.collectingAndThen(Collectors.toList(), lists -> lists.stream().flatMap(List::stream).collect(Collectors.toList())))));
 here the second parameter of  Collectors.mapping  uses  Collectors.collectingAndThen , which can be seen by the name. The first parameter is, of course, according to  Collectors.toList () . After collection, the second parameter traverses  List  again, flattens it and then forms  List  
.
 emmm, I don't like it very much either, but it just leads to  collectingAndThen . Maybe the subject can use it 
 in the future.
 second: also use  Collectors.mapping , but this time the second parameter uses  Collectors.reducing  
Map<String, List<String>> collect2 = conditions.stream()
                .collect(Collectors.groupingBy(Condition::getCondName, 
                        Collectors.mapping(Condition::getCondValue, 
                                Collectors.reducing(new ArrayList<>(), (l1, l2) -> Stream.concat(l1.stream(), l2.stream()).collect(Collectors.toList())))));
 here  Collectors.reducing  is the aggregation of data, which coincides with the current scenario. When the data in the stream is transformed from  Condition  to  List < String > , we want to find a way to merge different  List < String > . Therefore, this aggregation method  Collectors.reducing  is used here. The first parameter is the starting value, and the second parameter represents how to merge the two  list .
 the second method is better, but here I also think of the third method 
 third: instead of  Collectors.groupingBy , use  Collectors.toMap  
Map<String, List<String>> collect1 = conditions.stream().collect(
                Collectors.toMap(Condition::getCondName, Condition::getCondValue, (c1, c2) -> Stream.concat(c1.stream(), c2.stream()).collect(Collectors.toList())));
 the third way feels a bit simpler than the first two, but it skillfully uses the  toMap  method. The  toMap  method is generally used only when the data can have an one-to-one relationship. Most of the time, we only use the two-parameter method, that is, pass in how to get  key  of  map  and how to get the two  Function of  value  of  map . In business, data can be guaranteed to have an one-to-one relationship. If only two-parameter methods are called, but one-to-many cases do occur during actual use, then calling  toMap  two-parameter methods will report an error, so there is the  toMap  three-parameter method. The third parameter represents how to combine the  value  values of the same  key , so this is similar to the idea of the second method. Merge two sets 
 above is my answer, for reference only 
 by the way, plus, I generally don't like to write excessively long lambda expressions in the stream, because the flow is supposed to reflect the current process, not all the bad things go down, so the third way, the final collection merge, had better be written as a  BinaryOperator , after all, the method can also be a parameter or attribute 
.
public static final BinaryOperator<List<String>> listMergeMethod = (l1, l2) -> Stream.concat(l1.stream(), l2.stream()).collect(Collectors.toList());
Map<String, List<String>> collect1 = conditions.stream().collect(
                Collectors.toMap(Condition::getCondName, Condition::getCondValue, listMergeMethod));
 it makes me feel better.