Abstract / Brief Discussion
The presence of a map of map maps is doubtful when viewed from an object-oriented view, as it might seem like you are lacking in abstraction (i.e. you could create a Result class that encapsulates the results of a nested grouping). However, this is perfectly reasonable when viewed solely from a pure data-driven approach.
So, here I present two approaches: the first is purely data-oriented (with nested groupingBy calls, therefore, nested maps), and the second is more OO-friendly and does a better job of abstracting grouping criteria. Just choose the one that better reflects your the intentions and standards / traditions of coding and, more importantly, the one you like best.
Data Oriented Approach
For the first approach, you can simply groupingBy calls:
Map<String, Map<String, Map<String, List<Booker>>>> result = list.stream() .collect(Collectors.groupingBy(ProductDto::getStatus, Collectors.groupingBy(ProductDto::getCategory, Collectors.groupingBy(ProductDto::getType, Collectors.mapping( ProductDto::getBooker, Collectors.toList())))));
As you can see, the result is Map<String, Map<String, Map<String, List<Booker>>>> . This is because there can be more than one ProductDto instance with the same combination (status, category, type) .
Also, since you need Booker instances instead of ProductDto instances, I adapt the last groupingBy collector to return Booker instead of ProductDto s.
About abbreviation
If you need only one Booker instance instead of List<Booker> as the value of the innermost map, you will need a way to reduce Booker instances, that is, convert many instances to one using an associative operation (accumulating the sum of some attribute is the most common).
Object oriented approach
For the second approach, the presence of Map<String, Map<String, Map<String, List<Booker>>>> can be considered as bad practice or even as pure evil. Thus, instead of having a list card map card, you can only have one list card, whose keys represent a combination of the three properties that you want to group.
The easiest way to do this is to use List as a key, since lists already provide hashCode and equals implementations:
Map<List<String>, List<Booker>> result = list.stream() .collect(Collectors.groupingBy( dto -> Arrays.asList(dto.getStatus(), dto.getCategory(), dto.getType()), Collectors.mapping( ProductDto::getBooker, Collectors.toList())))));
If you use Java 9+, you can use List.of instead of Arrays.asList , as List.of returns a completely immutable and optimized list.