Java Streams Collector Operations Explained with Examples

Collectors are the workhorses of Stream terminal aggregation in Java 8. They let you join strings, group and partition data, build specific collection types, and compute statistics — all in a fluent, readable style.


1) Collectors.joining() — Concatenate elements

Concatenate stream elements into a single string with optional delimiter, prefix, and suffix.

List<String> words = Arrays.asList("apple", "banana", "cherry");
String joined = words.stream()
    .collect(Collectors.joining(", ", "[", "]"));
System.out.println(joined); // [apple, banana, cherry]
  

2) Collectors.groupingBy() — Group by classifier

Group elements based on a key extractor. Default downstream is toList().

List<Person> people = Arrays.asList(
    new Person("John",  "New York"),
    new Person("Wick",  "London"),
    new Person("Smith", "New York")
);

Map<String, List<Person>> byCity = people.stream()
    .collect(Collectors.groupingBy(Person::getCity));

System.out.println(byCity);
  

2.1) groupingBy with a downstream collector

Count how many people per city using counting(). You can plug in many downstream collectors like mapping, toSet, etc.

Map<String, Long> countsPerCity = people.stream()
    .collect(Collectors.groupingBy(Person::getCity, Collectors.counting()));
System.out.println(countsPerCity); // {London=1, New York=2}
  

2.2) groupingBy with custom map type + downstream

Produce a TreeMap of cities to uppercase names list, preserving key order.

Map<String, List<String>> namesByCity = people.stream()
    .collect(Collectors.groupingBy(
        Person::getCity,
        java.util.TreeMap::new,
        Collectors.mapping(p -> p.getName().toUpperCase(), Collectors.toList())
    ));
System.out.println(namesByCity);
  

3) Collectors.partitioningBy() — Split into true/false buckets

Partition elements by a predicate (exactly two groups).

List<Integer> nums = Arrays.asList(1,2,3,4,5,6);
Map<Boolean, List<Integer>> evensAndOdds = nums.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0));
System.out.println(evensAndOdds); // {false=[1, 3, 5], true=[2, 4, 6]}
  

4) Numeric aggregations — summingInt, averagingInt, summarizingInt

Quick math over a stream of numbers.

List<Integer> vals = Arrays.asList(1,2,3,4,5);
int sum = vals.stream().collect(Collectors.summingInt(n -> n));
double avg = vals.stream().collect(Collectors.averagingInt(n -> n));
java.util.IntSummaryStatistics stats = vals.stream()
    .collect(Collectors.summarizingInt(n -> n));
System.out.println(sum);   // 15
System.out.println(avg);   // 3.0
System.out.println(stats); // count=5, sum=15, min=1, average=3.0, max=5
  

5) Collectors.mapping() — Transform before collecting

Apply a mapping function as part of the collect phase (often with groupingBy).

List<String> upperNames = people.stream()
    .collect(Collectors.mapping(p -> p.getName().toUpperCase(), Collectors.toList()));
System.out.println(upperNames); // [JOHN, WICK, SMITH]
  

6) Building collections — toList, toSet, toCollection

Choose the target collection type explicitly.

List<Integer> raw = Arrays.asList(1,1,2,3,4,4,5);

// Set (remove duplicates, no defined iteration order)
java.util.Set<Integer> asSet = raw.stream().collect(Collectors.toSet());

// Specific collection type (LinkedList here)
java.util.LinkedList<Integer> linked = raw.stream()
    .collect(Collectors.toCollection(java.util.LinkedList::new));

System.out.println(asSet);
System.out.println(linked);
  

7) Collectors.toMap() — Build a Map (with merge + map type)

When keys may collide, supply a merge function; you can also pick the map implementation.

// Name -> City (merging keeps the first value for duplicate names)
java.util.Map<String, String> nameToCity = people.stream()
    .collect(Collectors.toMap(
        Person::getName,
        Person::getCity,
        (left, right) -> left // merge on duplicate key
    ));

// Preserve insertion order with LinkedHashMap
java.util.Map<String, String> ordered = people.stream()
    .collect(Collectors.toMap(
        Person::getName,
        Person::getCity,
        (l, r) -> l,
        java.util.LinkedHashMap::new
    ));

System.out.println(nameToCity);
System.out.println(ordered);
  

8) Collectors.counting() — Count elements

Standalone or as a downstream (e.g., with groupingBy).

List<String> letters = Arrays.asList("a","bb","ccc","dddd");
long total = letters.stream().collect(Collectors.counting());
System.out.println(total); // 4
  

Person class (used in examples)

class Person {
    private String name;
    private String city;

    Person(String name, String city) {
        this.name = name;
        this.city = city;
    }
    public String getName() { return name; }
    public String getCity() { return city; }

    @Override public String toString() {
        return "Person{" +
               "city='" + city + '\'' +
               ", name='" + name + '\'' +
               '}';
    }
}
  

When to use what?

  • joining → build one string from many values
  • groupingBy → bucketize by key; combine with downstreams like counting, mapping
  • partitioningBy → boolean split (true/false)
  • summing/averaging/summarizing → quick number stats
  • toSet/toCollection → pick the exact collection type
  • toMap → control key collisions and map implementation
  • counting → element totals or per-group counts

All examples are Java 8 compatible. Pair these collectors with intermediate ops (map, filter, flatMap) to build expressive, high-performance pipelines.

Labels: Java Streams, Java 8, Collectors, groupingBy, partitioningBy, toMap, joining, summarizingInt, mapping, toSet, toCollection, Stream API, DevForgeHub

Comments

Popular posts from this blog

Spring Boot on AWS EC2: Upload to S3 Securely Using IAM Role

Java Streams Intermediate Operations Explained with Examples

ConcurrentHashMap vs Synchronized HashMap in Java