Java 8 Collectors groupingBy Examples

Learn to use Collectors.groupingBy() method to group and aggregate the Stream elements similar to ‘GROUP BY‘ clause in the SQL.

1. Syntax of groupingBy() Method

The groupingBy() method returns a Collector implementing a “GROUP BY” operation on Stream elements and returns the result as a Map.

groupingBy(classifier)
groupingBy(classifier, collector)
groupingBy(classifier, supplier, collector)

We can pass the following arguments to this method:

  • classifier: maps input elements to map keys
  • collector: is the downstream reduction function. By default, Collectors.toList() is used which causes the grouped elements into a List.
  • supplier: provides a new empty Map into which the results will be inserted. By default, HashMap::new is used. We can use other maps such as TreeMap, LinkedHashMap or ConcurrentMap to insert additional behavior in the grouping process such as sorting.

We can use Collectors.groupingByConcurrent() if we wish to process the stream elements parallelly that uses the multi-core architecture of the machine and returns a ConcurrentMap. Except for concurrency, it works similarly to groupingBy() method.

groupingByConcurrent(classifier)
groupingByConcurrent(classifier, collector)
groupingByConcurrent(classifier, supplier, collector)

2. Collectors groupingBy Examples

For the demo purposes, we are creating two records Person and Department as follows.

record Person(int id, String name, double salary, Department department) {}
record Department(int id, String name) {}

And we are creating a List of persons that we will use to create Stream and collect the records in different groupings.

List<Person> persons = List.of(
    new Person(1, "Alex", 100d, new Department(1, "HR")),
    new Person(2, "Brian", 200d, new Department(1, "HR")),
    new Person(3, "Charles", 900d, new Department(2, "Finance")),
    new Person(4, "David", 200d, new Department(2, "Finance")),
    new Person(5, "Edward", 200d, new Department(2, "Finance")),
    new Person(6, "Frank", 800d, new Department(3, "ADMIN")),
    new Person(7, "George", 900d, new Department(3, "ADMIN")));

2.1. Grouping by Simple Condition

Let us start with a simple condition to understand the usage better. In follwoing example, we are grouping all the persons by department. This will create a Map<Department, List<Person>> where map key is department records and map value will be all the persons in that department.

Map<Department, List<Person>> map = persons.stream().collect(groupingBy(Person::department));

System.out.println(map);

The program output.

{
	Department[id=2, name=Finance]=[
	Person[id=3, ...], 
	Person[id=4, ...], 
	Person[id=5, ...], 

	Department[id=3, name=ADMIN]=[
	Person[id=6, ...], 
	Person[id=7, ...], 

	Department[id=1, name=HR]=[
	Person[id=1, ...], 
	Person[id=2, ...]
}

Similarly, if we wish to collect only the person ids in all departments then we can use Collectors.mapping() to collect all the person ids in a List rather than collecting the person records.

Map<Department, List<Integer>> map = persons.stream()
        .collect(groupingBy(Person::department, mapping(Person::id, toList())));

System.out.println(map);

The program output.

{
	Department[id=2, name=Finance]=[3, 4, 5], 
	Department[id=3, name=ADMIN]=[6, 7], 
	Department[id=1, name=HR]=[1, 2]
}

2.2. Grouping by Complex Condition

There may be cases when we have to apply a complex condition for grouping. In this case, the Map can represent the condition using a Java tuple and then group the matching elements as a List in Map value.

In following example, we want to group on distinct department and salary pairs. In the Map value, we will get a list of persons who have the same department and the same salary.

Map<Object, List<Integer>> map = persons.stream()
    .collect(groupingBy(person -> new Pair<>(person.salary(), person.department()),
        mapping(Person::id, toList())));

System.out.println(map);

The program output clearly tells that person 4 and 5 are the only who are in the same department and have the same salary.

{
	[900.0, Department[id=3, name=ADMIN]]=[7], 
	[800.0, Department[id=3, name=ADMIN]]=[6], 
	[200.0, Department[id=2, name=Finance]]=[4, 5], 
	[900.0, Department[id=2, name=Finance]]=[3], 
	[200.0, Department[id=1, name=HR]]=[2], 
	[100.0, Department[id=1, name=HR]]=[1]
}

2.3. Grouping with Counting

We can also aggregate the values by performing other operations such as counting(), averaging() summing() etc. This helps in getting the reduction operation on Map values to produce a single value.

In following example, we are counting all the persons in a department.

Map<Department, Long> map = persons.stream()
    .collect(groupingBy(Person::department, counting()));

System.out.println(map);

The program output.

{
	Department[id=2, name=Finance]=3, 
	Department[id=3, name=ADMIN]=2, 
	Department[id=1, name=HR]=2
}

In same way, we can find count all the persons having same salary.

Map<Double, Long> map = persons.stream()
    .collect(groupingBy(Person::salary, counting()));

System.out.println(map);

The program output.

{800.0=1, 200.0=3, 100.0=1, 900.0=2}

2.4. Grouping with Average

It is possible to perform other aggregate operations such as finding the average salary in each department.

Map<Department, Double> map = persons.stream()
    .collect(groupingBy(Person::department, averagingDouble(Person::salary)));

System.out.println(map);

The program output.

{
	Department[id=2, name=Finance]=433.3333333333333, 
	Department[id=3, name=ADMIN]=850.0, 
	Department[id=1, name=HR]=150.0
}

2.5. Grouping with Max/Min

To find the maximum or minimum value for the grouped values, use maxBy() or minBy() collectors and pass the Comparator argument to compare the required field values.

In following example, we are finding the max salaried person in each department.

Map<Department, Optional<Person>> map = persons.stream()
    .collect(groupingBy(Person::department, maxBy(Comparator.comparingDouble(Person::salary))));

System.out.println(map);

The program output.

{
	Department[id=2, name=Finance]=Optional[Person[id=3, name=Charles, salary=900.0,...]],
	Department[id=3, name=ADMIN]=Optional[Person[id=7, name=George, salary=900.0, ...]], 
	Department[id=1, name=HR]=Optional[Person[id=2, name=Brian, salary=200.0, ...]]
}

2.6. Grouping with Filtering

The Stream.filter() method filters out all the non-matching elements from the stream before passing it to the next operation. This may not be the desired solution.

Consider the following example where we are grouping all persons by the department; whose salary is greater than 300.

Map<Department, Long> map = persons.stream()
    .filter(p -> p.salary() > 300d)
    .collect(groupingBy(Person::department, counting()));

System.out.println(map);

The program output.

{
	Department[id=2, name=Finance]=1, 
	Department[id=3, name=ADMIN]=2
}

The above program output omits the department-1 altogether because there was no person matching the condition in that department. But if we want to list all such Map keys where there is no matching value exists than we can use Collectors.filtering() method that applies the filter while adding values in to Map.

Map<Department, Long> map = persons.stream()
    .collect(groupingBy(Person::department, filtering(p -> p.salary() > 300d, counting())));

System.out.println(map);

The program output. Now the department-1 is also listed with zero matching records.

{
	Department[id=2, name=Finance]=1, 
	Department[id=3, name=ADMIN]=2, 
	Department[id=1, name=HR]=0
}

3. Conclusion

The Collectors’s groupBy() method is excellent for grouping the Stream elements by various conditions and performing all kinds of aggregate operations on the Map values. We can use combinations of Collectors to perform any kind of grouping as shown in the above examples.

Happy Learning !!

Sourcecode on Github

Leave a Reply

0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial