Saturday, July 31, 2021

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. 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 ways to aggregate data, it has a real benefit, coupled that with the various overloaded version of groupingBy() method which also allows you to perform grouping objects concurrently by using concurrent Collectors.

In this Java 8 tutorial, you will learn how to group by a 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.

And, If you are serious about improving Java functional programming skills then I highly recommend you check out these best Java Collections and Stream courses, which explains both Functional Programming and Java Stream fundamentals in good detail



How to group objects in Java 8? Example

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 the grouping criterion. In the earlier version, you have to write the same 5 to 6 lines of code to create a group of different criteria.

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 the SQL style group by 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 a grouping list of Person objects 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 criteria 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.



Related Java 8 Tutorials
If you are interested in learning more about new features of Java 8, here are my earlier articles covering some of the important concepts of Java 8:
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • How to use Stream class in Java 8 (tutorial)
  • How to use filter() method in Java 8 (tutorial)
  • How to use forEach() method in Java 8 (example)
  • How to join String in Java 8 (example)
  • How to convert List to Map in Java 8 (solution)
  • How to use peek() method in Java 8 (example)
  • 5 Books to Learn Java 8 from Scratch (books)
  • How to convert the stream to array in Java 8 (tutorial)
  • Java 8 Certification FAQ (guide)
  • Java 8 Mock Exams and Practice Test (test)

7 comments :

Unknown 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.

samt said...

how do you print a new line between each entry in the list so the output would be similar to the following:
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)]
}

djbilal said...

Thank you so much specially for the logic!!!!

javin paul said...

your welcome @Djiblal. happy to hear that you find this tutorial useful.

Anonymous said...

Anyone know how to solve this question ?

Sample data = Arrays.asList("Collection","java","spring","hibernate","jpa");

Requirement - Change the middle value to uppercase.
Conditions - 1> length is odd, change middle character to uppercase.
2> length is even then change middle two character to uppercase

Eg : jpa -> jPa
spring -> spRIng.


Post a Comment