Skip to content

Group map values but keys are same

I have a map like this. Map<long,List<Student>> studentMap

Key is a number 1,2,3,4… Student object is :

public class Student {
 private long addressNo;
 private String code;
 private BigDecimal tax;
 private String name;
 private String city;

 // getter and setters` 

What i want to do is to convert it Map<long,List<StudentInfo>> studentInfoMap object and group id, addressNo and code fields.I want key are same for both maps.

I can group the map by using these codes but summingDouble is not working for BigDecimal.Also I cannot convert my studentMap to studentInfoMap.:(


My studentInfo object is :

public class StudentInfo {
  private long addressNo;
  private String code;
  private BigDecimal tax;

  // getter and setters` 



For a one-to-one conversion from Student to StudentInfo:

class StudentInfo {
    public static StudentInfo of(Student student) {
        StudentInfo si = new StudentInfo();
        return si;

To convert from one Map to the other:

Map<Long,List<Student>> studentMap = ...
Map<Long,List<StudentInfo>> studentInfoMap = studentMap.entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey, //same key
                    entry -> entry.getValue().stream()
                            .map(StudentInfo::of)    //conversion of Student to StudentInfo
                            .collect(Collectors.toList()) //or simply `.toList()` as of Java 16

Now your grouping….

From the JavaDoc for<T> public abstract <R, A> R collect(<? super T, A, R> collector):

The following will classify Person objects by city:

Map<String, List<Person>> peopleByCity
    = personStream.collect(Collectors.groupingBy(Person::getCity));

The following will classify Person objects by state and city, cascading two Collectors together:

Map<String, Map<String, List<Person>>> peopleByStateAndCity
    = personStream.collect(Collectors.groupingBy(Person::getState,

Note how the last example produces a Map with another Map as its values.

Now, summingDouble over StudentInfo::getTax produces a BigDecimal, not a Map. Replacing with groupingBy will work to classify Students that have the same amount for getTax:

Map<String, Map<Long, Map<BigDecimal, List<StudentInfo>>>> summary = 
            .flatMap(List::stream)  //YOU ALSO NEED THIS

Edit: Retaining the 1,2,3,4 original keys

To retain the original keys you can iterate or stream the original entrySet, which contains both key and value:

Map<Long,Map<String, Map<Long, Map<BigDecimal, List<StudentInfo>>>>> summaryWithKeys =
        studentInfoMap.entrySet().stream() //NOTE streaming the entrySet not just values
                        Collectors.toMap(Map.Entry::getKey, //Original Key with toMap
                                entry -> entry.getValue().stream() //group the value-List members

Just as an exercise, if you want a flat map (Map<MyKey,List>) you need a composite key MyKey

As per my comment, if you are looking to have a single flat Map, you could design a composite key, which would need to implement both equals() and hashCode() to contract. For example, this is what Lombok would generate for StudentInfo (yes, its easier to depend on lombok and use @EqualsAndHashCode):

public boolean equals(final Object o) {
        if(o == this) return true;
        if(!(o instanceof StudentInfo)) return false;
        final StudentInfo other = (StudentInfo) o;
        if(!other.canEqual((Object) this)) return false;
        if(this.getAddressNo() != other.getAddressNo()) return false;
        final Object this$code = this.getCode();
        final Object other$code = other.getCode();
        if(this$code == null ? other$code != null : !this$code.equals(other$code)) return false;
        final Object this$tax = this.getTax();
        final Object other$tax = other.getTax();
        if(this$tax == null ? other$tax != null : !this$tax.equals(other$tax)) return false;
        return true;
    protected boolean canEqual(final Object other) {return other instanceof StudentInfo;}
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $addressNo = this.getAddressNo();
        result = result * PRIME + (int) ($addressNo >>> 32 ^ $addressNo);
        final Object $code = this.getCode();
        result = result * PRIME + ($code == null ? 43 : $code.hashCode());
        final Object $tax = this.getTax();
        result = result * PRIME + ($tax == null ? 43 : $tax.hashCode());
        return result;

You might then use StudentInfo as the composite key as follows:

Map<Long, List<Student>> studentMap = ...
Map<StudentInfo,List<Student>>> summaryMap = studentMap.values().stream()

This means that you now have a nested map referenced by the composite key. Students that have exactly the same addressNo, code and tax will be part of the List referenced by each such key.

Edit: Retaining original keys

Similarly, if you wanted to retain the original keys, you could either add them into the composite key, or similar as above:

Map<Long, List<Student>> studentMap = ...
Map<Long, Map<StudentInfo,List<Student>>>> summaryMap = studentMap.entrySet().stream()