How to do GROUP BY in Java 8? Collectors.groupingBy() Example

Java 8 now directly allows you to do GROUP BY in Java by using Collectors.groupingBy() method. GROUP BY is a very useful aggregate operation from SQL. It allows you to group records on certain criteria. How do you group by in Java? For example, suppose you have a list of Persons, How do you group persons by their city e.g. London, Paris or Tokyo? Well, we can do that by using a for loop, checking each person and putting them on a list of HashMap with the same city, but in Java 8, you don't need to hack your way like that, you have a much cleaner solution. You can use Stream and Collector which provides groupingBy() method to do this. Since its one of the most common way to aggregate data, it has a real benefit, coupled that with the various overloaded version of groupingBy() method which also allow you to perform grouping objects concurrently by using concurrent Collectors.

In this Java 8 tutorial, you will learn how to group by list of objects based on their properties. For those, who think Java 8 is just about lambda expression, it's not true, there are so many goodies released in JDK 1.8 and you will be amazed once you start using them.

If you want to explore further, I suggest you take a look at Cay S. Horstmann's introductory book, Java SE 8 for Really Impatient. One of the best book to learn new concepts and API changes of JDK 8.

JDK 8 GroupBy Example


It's not only tells about new features of Java 8 but also about some goodies from Java 7 release e.g. new File IO, improved exception handling, automatic resource handling and much more. Here is a handy list of Java 7 features.



How to group objects in Java 8

Here is our sample program to group objects on their properties in Java 8 and earlier version. First, we'll take a look at how could we do this in pre-Java 8 world and later we'll Java 8 example of the group by. By looking at both approaches you can realize that Java 8 has really made your task much easier.


In Java SE 6 or 7,  in order to create a group of objects from a list, you need to iterate over the list, check each element and put them into their own respective list. You also need a Map to store these groups. When you got the first item for a new group, you create a list and put that item on the list, but when the group already exists in Map then you just retrieve it and store your element into it.

The code is not difficult to write but it takes 5 to 6 lines to do that and you have to do null check everywhere to avoid NullPointerException. Compare that to one line code of Java 8, where you get the stream from the list and used a Collector to group them. All you need to do is pass the grouping criterion to the collector and its done.

This way, you can create multiple groups by just changing grouping criterion. In the earlier version, you have to write same 5 to 6 lines of code to create a group of different criterion.

How to create groups of Objects in Java 8

You can even perform several aggregate functions like sum(), count(), max(), min() in individual groups by taking advantage of new Stream API, as shown in my earlier streams examples. After all individual groups are just a list of objects and you can get the stream by just calling stream() method on that. In short, you can now do SQL style group by in Java without using any loop.

Java Program to Group Objects

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;


/**
 * Java Program to demonstrate how to do group by in Java 8 using
 * groupingBy() method of Collector class and Stream.

 * @author Javin Paul
 */
public class GroupByDemoInJava8 {

    public static void main(String args[]) throws IOException {

        List<Person> people = new ArrayList<>();
        people.add(new Person("John", "London", 21));
        people.add(new Person("Swann", "London", 21));
        people.add(new Person("Kevin", "London", 23));
        people.add(new Person("Monobo", "Tokyo", 23));
        people.add(new Person("Sam", "Paris", 23));
        people.add(new Person("Nadal", "Paris", 31));
        
        // Now let's group all person by city in pre Java 8 world        
        Map<String,List<Person>> personByCity = new HashMap<>();
        
        for(Person p : people){
            if(!personByCity.containsKey(p.getCity())){
                personByCity.put(p.getCity(), new ArrayList<>());                
            }
            personByCity.get(p.getCity()).add(p);
        }
        
        System.out.println("Person grouped by cities : " + personByCity);
        
        // Let's see how we can group objects in Java 8
        personByCity =  people.stream()
                         .collect(Collectors.groupingBy(Person::getCity));
        System.out.println("Person grouped by cities in Java 8: " 
                         + personByCity);
        
        // Now let's group person by age
        
        Map<Integer,List<Person>> personByAge = people.stream()
                          .collect(Collectors.groupingBy(Person::getAge));
        System.out.println("Person grouped by age in Java 8: " + personByAge);
    }


}

class Person{
    private String name;
    private String city;
    private int age;

    public Person(String name, String city, int age) {
        this.name = name;
        this.city = city;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return String.format("%s(%s,%d)", name, city, age);
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 79 * hash + Objects.hashCode(this.name);
        hash = 79 * hash + Objects.hashCode(this.city);
        hash = 79 * hash + this.age;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Person other = (Person) obj;
        if (!Objects.equals(this.name, other.name)) {
            return false;
        }
        if (!Objects.equals(this.city, other.city)) {
            return false;
        }
        if (this.age != other.age) {
            return false;
        }
        return true;
    }
    
    
}

Output :
Person grouped by cities : {
Tokyo=[Monobo(Tokyo,23)],
London=[John(London,21), Swann(London,21), Kevin(London,23)],
Paris=[Sam(Paris,23), Nadal(Paris,31)]
}

Person grouped by cities in Java 8: {
Tokyo=[Monobo(Tokyo,23)], 
London=[John(London,21), Swann(London,21), Kevin(London,23)], 
Paris=[Sam(Paris,23), Nadal(Paris,31)]
}

Person grouped by age in Java 8: {
21=[John(London,21), Swann(London,21)], 
23=[Kevin(London,23), Monobo(Tokyo,23), Sam(Paris,23)], 
31=[Nadal(Paris,31)]
}

In this example, we are grouping list of Person object by their city. In our list, we have 3 persons from London, 2 from Paris and one from Tokyo.  After grouping them by the city, you can see that they are in their own List, there is one Person in the list of Tokyo, 3 persons in the list of London and 2 persons in the list of Paris. Both Java 7 and Java 8 example has produced identical groups.

Later, we have also created another group by dividing them by their age and you can see that we have 3 groups for different age groups, 21, 23 and 31.


That's all about how to do group by in Java 8. You can now more easily create a group of objects on arbitrary criterion than ever before. Java 8 Collectors class also provide several overloaded version of the groupingBy() function for more sophisticated grouping. You can even do a group by concurrently by using groupingByConcurrent() method from java.util.streams.Collectors class.

Recommended resources for further reading

  • Javadoc of Collectors to learn more about different groupingBy() methods. (see here)
  • Java 8 in Action: Lambdas, Streams, and functional-style programming (see here)
  • Mastering Lambdas: Java Programming in a Multicore World (see here)


3 comments :

PSI A said...

How do you perform multi-level group by? for example Country --> State --> City --> Age and then count people at each level?


Pravat said...

most important part of the code is not visible

Javin Paul said...

@Pravat, thanks for pointing out, I have made the collector part of code wrapped now.

Post a Comment