[{"data":1,"prerenderedAt":139},["ShallowReactive",2],{"qa-\u002Fjava\u002Fstreams-functional\u002Fcollectors-grouping":3},{"page":4,"siblings":123,"blog":136},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":113,"related":114,"seo":115,"seoDescription":116,"stem":117,"subtopic":118,"topic":119,"topicSlug":120,"updated":121,"__hash__":122},"qa\u002Fjava\u002Fstreams-functional\u002Fcollectors-grouping.md","Collectors Grouping",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"hard","md","Java","java",{},true,3,"\u002Fjava\u002Fstreams-functional\u002Fcollectors-grouping",[23,28,32,37,41,45,49,53,57,61,65,69,73,77,81,85,89,93,97,101,105,109],{"id":24,"difficulty":25,"q":26,"a":27},"collect-overview","medium","What does the collect() terminal operation do?","`collect` is a **mutable reduction** terminal operation: it folds the stream's\nelements into a **mutable result container** (a `List`, `Map`, `StringBuilder`,\netc.) by repeatedly accumulating into it. It takes a **`Collector`**, and the\n`java.util.stream.Collectors` class is a factory of ready-made ones.\n\n```java\nList\u003CString> upper = names.stream()\n    .map(String::toUpperCase)\n    .collect(Collectors.toList());\n```\n\nUnlike `reduce`, which combines **immutable** values, `collect` mutates a\ncontainer in place, which is far more efficient for building collections and\nstrings. Reach for the `Collectors` factory first — only write a custom\ncollector when nothing there fits.\n",{"id":29,"difficulty":14,"q":30,"a":31},"collector-anatomy","What are the four components of a Collector?","A `Collector\u003CT, A, R>` is defined by **four functions** (`T` = input element,\n`A` = mutable accumulation type, `R` = final result):\n\n| Component | Role |\n| --------- | ---- |\n| **supplier** | creates a new empty mutable container (`A`) |\n| **accumulator** | folds one element into the container |\n| **combiner** | merges two partial containers (used in parallel) |\n| **finisher** | transforms the container `A` into the result `R` |\n\n```java\n\u002F\u002F toList conceptually: supplier=ArrayList::new,\n\u002F\u002F accumulator=List::add, combiner=addAll, finisher=identity\n```\n\nA fifth piece, **characteristics**, hints at optimizations. The **combiner**\nis what makes a collector parallel-safe; the **finisher** is skipped entirely\nwhen the container already *is* the result (`IDENTITY_FINISH`).\n",{"id":33,"difficulty":34,"q":35,"a":36},"to-list-set-collection","easy","How do toList, toSet and toCollection differ?","All three gather elements into a collection, differing in the container:\n\n- **`toList()`** — accumulates into a `List` (an `ArrayList` in practice).\n- **`toSet()`** — accumulates into a `Set` (a `HashSet`), dropping duplicates,\n  with no order guarantee.\n- **`toCollection(supplier)`** — accumulates into **whatever collection** you\n  supply, when you need a specific type.\n\n```java\nList\u003CString> list = s.collect(Collectors.toList());\nSet\u003CString>  set  = s.collect(Collectors.toSet());\nTreeSet\u003CString> sorted =\n    s.collect(Collectors.toCollection(TreeSet::new)); \u002F\u002F sorted, no dups\n```\n\nUse `toCollection` when the default type won't do — e.g. a `TreeSet` for\nordering or a `LinkedList` for insertion semantics.\n",{"id":38,"difficulty":25,"q":39,"a":40},"collectors-tolist-vs-stream-tolist","What is the difference between Collectors.toList() and Stream.toList()?","Both produce a `List`, but the **mutability and null handling** differ:\n\n| | `collect(Collectors.toList())` | `stream.toList()` (Java 16+) |\n| - | - | - |\n| Mutability | **modifiable** (`ArrayList`) | **unmodifiable** |\n| Allows `null` | yes | yes |\n| Conciseness | verbose | one method |\n\n```java\nList\u003CInteger> a = nums.stream().collect(Collectors.toList());\na.add(99);                      \u002F\u002F OK — mutable\n\nList\u003CInteger> b = nums.stream().toList();\nb.add(99);                      \u002F\u002F UnsupportedOperationException\n```\n\nPrefer the newer `stream().toList()` for read-only results; it's shorter and\nits immutability prevents accidental mutation. Use `Collectors.toList()` only\nwhen you genuinely need to modify the result afterward.\n",{"id":42,"difficulty":25,"q":43,"a":44},"to-unmodifiable","How do you collect into an unmodifiable collection?","Java 10 added **`toUnmodifiableList`**, **`toUnmodifiableSet`** and\n**`toUnmodifiableMap`**, which return collections that throw\n`UnsupportedOperationException` on any mutation. They also **reject `null`**\nelements (throwing `NullPointerException`).\n\n```java\nList\u003CString> ro = names.stream()\n    .filter(n -> n.length() > 3)\n    .collect(Collectors.toUnmodifiableList());\nro.clear(); \u002F\u002F UnsupportedOperationException\n```\n\nThese are the collector equivalents of `List.of`\u002F`Set.of`\u002F`Map.of`. Since\nJava 16 you can also just use `stream().toList()` for an unmodifiable `List`.\n",{"id":46,"difficulty":25,"q":47,"a":48},"to-map-basics","How does Collectors.toMap work?","`toMap` builds a `Map` from each element using a **key mapper** and a **value\nmapper** — two functions that derive the key and value from each element.\n\n```java\nMap\u003CString, Integer> byName = people.stream()\n    .collect(Collectors.toMap(\n        Person::name,        \u002F\u002F key mapper\n        Person::age));       \u002F\u002F value mapper\n```\n\nBy default the result is a `HashMap`. The catch interviewers probe is **duplicate\nkeys**: if two elements map to the same key, the two-argument `toMap` throws an\n**`IllegalStateException`** (\"Duplicate key\") — you must supply a merge function\nto resolve collisions.\n",{"id":50,"difficulty":14,"q":51,"a":52},"to-map-merge","What is the merge function in toMap and when do you need it?","The **three-argument** `toMap` takes a **merge function** `(existing, new) ->\nresult` invoked whenever two elements produce the **same key**. Without it,\nduplicate keys throw `IllegalStateException`.\n\n```java\n\u002F\u002F two-arg: throws IllegalStateException on duplicate \"Bob\"\n\u002F\u002F three-arg: resolve the clash\nMap\u003CString, Integer> totals = orders.stream()\n    .collect(Collectors.toMap(\n        Order::customer,\n        Order::amount,\n        Integer::sum));        \u002F\u002F merge: add amounts for same customer\n```\n\nA **fourth** argument supplies the map type (e.g. `TreeMap::new`) for ordering\nor a specific implementation. Always provide a merge function when keys aren't\nguaranteed unique — it's the single most common `toMap` bug.\n",{"id":54,"difficulty":25,"q":55,"a":56},"grouping-by-basics","What does Collectors.groupingBy do?","`groupingBy` takes a **classifier function** and partitions elements into a\n`Map\u003CK, List\u003CT>>` keyed by the classifier's result — every element with the\nsame key lands in the same list.\n\n```java\nMap\u003CDepartment, List\u003CEmployee>> byDept = employees.stream()\n    .collect(Collectors.groupingBy(Employee::department));\n```\n\nIt's the SQL `GROUP BY` of streams. The default value container is a `List` and\nthe default map is a `HashMap`. This single-argument form is the gateway — the\nreal power comes from adding a **downstream collector** to reshape each group.\n",{"id":58,"difficulty":14,"q":59,"a":60},"grouping-by-downstream","What is a downstream collector in groupingBy?","The two-argument `groupingBy(classifier, downstream)` applies a **second\ncollector to each group** instead of just collecting elements into a list. This\nlets you count, sum, average, or otherwise reduce each bucket.\n\n```java\n\u002F\u002F count per department\nMap\u003CDept, Long> counts = emps.stream()\n    .collect(Collectors.groupingBy(Employee::dept, Collectors.counting()));\n\n\u002F\u002F sum of salaries per department\nMap\u003CDept, Integer> totals = emps.stream()\n    .collect(Collectors.groupingBy(Employee::dept,\n             Collectors.summingInt(Employee::salary)));\n```\n\nCommon downstreams: `counting`, `summingInt\u002FLong\u002FDouble`,\n`averagingInt\u002FDouble`, `mapping`, `toSet`, `joining`, `maxBy`\u002F`minBy`,\n`reducing`. Downstream collectors are the heart of expressive aggregation.\n",{"id":62,"difficulty":14,"q":63,"a":64},"grouping-by-mapping","How do you transform group elements with the mapping downstream collector?","`Collectors.mapping(mapper, downstream)` applies a **transform** to each element\n*before* it reaches a further downstream collector — it adapts a collector to a\ndifferent input type. It's how you collect a **field** of each group member\nrather than the whole object.\n\n```java\n\u002F\u002F names of employees in each department\nMap\u003CDept, List\u003CString>> names = emps.stream()\n    .collect(Collectors.groupingBy(Employee::dept,\n             Collectors.mapping(Employee::name, Collectors.toList())));\n```\n\nThink of `mapping` as a `map()` embedded inside a collector. It pairs naturally\nwith `toList`, `toSet`, or `joining` to project group members.\n",{"id":66,"difficulty":25,"q":67,"a":68},"grouping-by-counting","How do you count elements per group?","Combine `groupingBy` with the **`counting()`** downstream collector. `counting`\nreturns a `Long`, so the result is `Map\u003CK, Long>`.\n\n```java\nMap\u003CString, Long> wordFreq = words.stream()\n    .collect(Collectors.groupingBy(\n        Function.identity(),     \u002F\u002F group by the word itself\n        Collectors.counting())); \u002F\u002F count occurrences\n\u002F\u002F {\"the\"=4, \"cat\"=2, ...}\n```\n\n`Function.identity()` is the idiom for grouping elements **by themselves** — a\nfrequency map. This is the canonical \"count occurrences\" stream pattern.\n",{"id":70,"difficulty":14,"q":71,"a":72},"nested-grouping","How do you do multi-level (nested) grouping?","Because the downstream of `groupingBy` can be **another `groupingBy`**, you\nnest them to build a multi-level map — exactly like a SQL `GROUP BY` on two\ncolumns.\n\n```java\n\u002F\u002F group by department, then by city within each department\nMap\u003CDept, Map\u003CString, List\u003CEmployee>>> nested = emps.stream()\n    .collect(Collectors.groupingBy(Employee::dept,\n             Collectors.groupingBy(Employee::city)));\n```\n\nYou can keep nesting or end with an aggregating downstream\n(`Map\u003CDept, Map\u003CString, Long>>` via `counting()`). The outer classifier forms\nthe first key level; each inner collector handles the next.\n",{"id":74,"difficulty":25,"q":75,"a":76},"partitioning-by","What is partitioningBy and how does it differ from groupingBy?","`partitioningBy` splits a stream into **exactly two groups** using a\n**predicate**, returning a `Map\u003CBoolean, List\u003CT>>` with keys `true` and\n`false`. It's a specialized, optimized `groupingBy` for the boolean case.\n\n```java\nMap\u003CBoolean, List\u003CInteger>> parts = nums.stream()\n    .collect(Collectors.partitioningBy(n -> n % 2 == 0));\nparts.get(true);   \u002F\u002F evens\nparts.get(false);  \u002F\u002F odds\n```\n\nKey difference from `groupingBy`: the map **always contains both keys**, even\nwhen one partition is empty (`groupingBy` omits empty groups). It also accepts a\ndownstream collector: `partitioningBy(pred, counting())`.\n",{"id":78,"difficulty":34,"q":79,"a":80},"joining","How does Collectors.joining work?","`joining` concatenates the stream's **`CharSequence`** elements into one\n`String`. It has three forms: no-arg (concatenate), one-arg (delimiter), and\nthree-arg (**delimiter, prefix, suffix**).\n\n```java\nString csv = names.stream()\n    .collect(Collectors.joining(\", \"));          \u002F\u002F \"Ann, Bob, Cy\"\n\nString list = names.stream()\n    .collect(Collectors.joining(\", \", \"[\", \"]\")); \u002F\u002F \"[Ann, Bob, Cy]\"\n```\n\nIt only accepts `CharSequence`, so `map(Object::toString)` first if your\nelements aren't strings. Internally it uses a `StringBuilder`, making it far\nmore efficient than reducing with `+`.\n",{"id":82,"difficulty":25,"q":83,"a":84},"summing-averaging","What do the summing and averaging collectors return?","`summingInt\u002FLong\u002FDouble` apply a value-extracting function and **sum** the\nresults; `averagingInt\u002FLong\u002FDouble` compute the **mean**. Each takes a\n`ToIntFunction`-style mapper.\n\n```java\nint total = orders.stream()\n    .collect(Collectors.summingInt(Order::quantity));      \u002F\u002F sum -> int\u002Flong\n\ndouble avg = orders.stream()\n    .collect(Collectors.averagingInt(Order::quantity));    \u002F\u002F mean -> double\n```\n\nNote `summingInt` returns the primitive's boxed total, while **all\n`averaging*` variants return `Double`** (a mean is rarely integral). These shine\nas `groupingBy` downstreams for per-group totals and means.\n",{"id":86,"difficulty":25,"q":87,"a":88},"summarizing-stats","What is summarizingInt and IntSummaryStatistics?","`summarizingInt\u002FLong\u002FDouble` compute **count, sum, min, max, and average in a\nsingle pass**, returning an `IntSummaryStatistics` (or `Long`\u002F`Double` variant)\nthat exposes all five.\n\n```java\nIntSummaryStatistics stats = employees.stream()\n    .collect(Collectors.summarizingInt(Employee::salary));\nstats.getCount();    \u002F\u002F 50\nstats.getSum();      \u002F\u002F 3_200_000\nstats.getMin();      \u002F\u002F 40_000\nstats.getMax();      \u002F\u002F 180_000\nstats.getAverage();  \u002F\u002F 64000.0\n```\n\nUse it instead of running several collectors when you need multiple statistics —\nit traverses the stream once. (`stream().mapToInt(...).summaryStatistics()` is\nthe equivalent without `collect`.)\n",{"id":90,"difficulty":14,"q":91,"a":92},"reducing-collector","When would you use the reducing collector instead of Stream.reduce?","`Collectors.reducing` is a **collector-form** of reduction. Standalone it\nduplicates `Stream.reduce`, so its real purpose is being a **downstream\ncollector** inside `groupingBy`\u002F`partitioningBy`, where you can't drop to\n`Stream.reduce`.\n\n```java\n\u002F\u002F highest-paid employee per department\nMap\u003CDept, Optional\u003CEmployee>> top = emps.stream()\n    .collect(Collectors.groupingBy(Employee::dept,\n             Collectors.reducing(BinaryOperator.maxBy(\n                 Comparator.comparingInt(Employee::salary)))));\n```\n\nFor top-level reductions prefer `Stream.reduce` — it's clearer. Reach for\n`Collectors.reducing` (or the dedicated `maxBy`\u002F`minBy`) only as a downstream.\n",{"id":94,"difficulty":14,"q":95,"a":96},"collecting-and-then","What does collectingAndThen do?","`collectingAndThen(downstream, finisher)` runs a collector, then applies a\n**finishing transformation** to its result. It's how you adapt a collector's\noutput — most commonly to wrap a collection as **unmodifiable** or to extract a\nvalue from an `Optional`.\n\n```java\nList\u003CString> immutable = names.stream()\n    .collect(Collectors.collectingAndThen(\n        Collectors.toList(),\n        Collections::unmodifiableList));\n\n\u002F\u002F unwrap the maxBy Optional per group\nMap\u003CDept, Employee> top = emps.stream()\n    .collect(Collectors.groupingBy(Employee::dept,\n             Collectors.collectingAndThen(\n                 Collectors.maxBy(Comparator.comparingInt(Employee::salary)),\n                 Optional::get)));\n```\n\nIt effectively bolts a custom **finisher** onto an existing collector without\nwriting one from scratch.\n",{"id":98,"difficulty":14,"q":99,"a":100},"filtering-flatmapping","What are the filtering and flatMapping downstream collectors?","Added in **Java 9**, both are downstream collectors that solve a real problem:\nfiltering *before* the stream's `filter()` would silently drop empty groups.\n\n- **`filtering(predicate, downstream)`** — keeps only matching elements per\n  group, but **preserves the group key even if it becomes empty** (unlike a\n  pre-`filter`, which removes the whole bucket).\n- **`flatMapping(mapper, downstream)`** — flattens each element to a stream and\n  collects the results, the collector-level `flatMap`.\n\n```java\nMap\u003CDept, List\u003CEmployee>> highEarners = emps.stream()\n    .collect(Collectors.groupingBy(Employee::dept,\n             Collectors.filtering(e -> e.salary() > 100_000,\n                 Collectors.toList())));\n```\n\nUse `filtering` over an upstream `filter` whenever you need **every group key\npresent**, even with no surviving members.\n",{"id":102,"difficulty":14,"q":103,"a":104},"teeing","What is the teeing collector?","`teeing` (Java 12) feeds each element to **two downstream collectors at once**,\nthen **merges their two results** with a `BiFunction`. It computes two\naggregates in a single pass.\n\n```java\n\u002F\u002F average = sum \u002F count, in one traversal\ndouble avg = nums.stream()\n    .collect(Collectors.teeing(\n        Collectors.summingDouble(n -> n),  \u002F\u002F result 1: sum\n        Collectors.counting(),             \u002F\u002F result 2: count\n        (sum, count) -> sum \u002F count));     \u002F\u002F merge\n```\n\nIt's ideal when two statistics depend on the same stream and you want to avoid\ncollecting to a list or streaming twice — e.g. min and max, or sum and count.\n",{"id":106,"difficulty":14,"q":107,"a":108},"collector-characteristics","What are Collector characteristics?","Characteristics are **optimization hints** in a collector's `characteristics()`\nset that tell the stream pipeline what shortcuts are safe:\n\n- **`UNORDERED`** — the result doesn't depend on encounter order (e.g.\n  `toSet`), so the pipeline may reorder for speed.\n- **`CONCURRENT`** — the accumulator can be called on **one shared container\n  from multiple threads** (e.g. `groupingByConcurrent`), avoiding merges.\n- **`IDENTITY_FINISH`** — the finisher is the identity, so the container *is*\n  the result and the finisher step is **skipped**.\n\n```java\nCollectors.toList();  \u002F\u002F IDENTITY_FINISH\nCollectors.toSet();   \u002F\u002F UNORDERED, IDENTITY_FINISH\n```\n\nYou rarely set these directly — they matter mostly when writing a **custom**\ncollector or reasoning about parallel-stream performance.\n",{"id":110,"difficulty":14,"q":111,"a":112},"custom-collector","How do you write a custom Collector with Collector.of?","Use **`Collector.of(supplier, accumulator, combiner, [finisher], characteristics...)`**,\npassing the four functions directly. The combiner is mandatory so the collector\nworks in parallel.\n\n```java\n\u002F\u002F a custom collector that joins names into a single uppercase CSV string\nCollector\u003CString, StringJoiner, String> upperCsv = Collector.of(\n    () -> new StringJoiner(\", \"),          \u002F\u002F supplier\n    (j, s) -> j.add(s.toUpperCase()),      \u002F\u002F accumulator\n    StringJoiner::merge,                   \u002F\u002F combiner\n    StringJoiner::toString);               \u002F\u002F finisher\n\nString result = names.stream().collect(upperCsv);\n```\n\nOmit the finisher when the container already is the result (an\n`IDENTITY_FINISH` collector). In practice, prefer composing existing\n`Collectors` (`mapping`, `collectingAndThen`, `teeing`) — write a fully custom\ncollector only when no combination fits.\n",22,null,{"description":11},"Java Collectors interview questions — collect and the Collectors factory, toList\u002FtoMap\u002FtoSet, groupingBy and partitioningBy, downstream collectors, joining, counting and summing, and writing a custom collector.","java\u002Fstreams-functional\u002Fcollectors-grouping","Collectors & Grouping","Streams & Functional","streams-functional","2026-06-20","mR8XsxMuXt6DdH_AH7BfjvaWXSTrBmLJD6TV3cmT6Ls",[124,128,131,132],{"subtopic":125,"path":126,"order":127},"Lambdas & Functional Interfaces","\u002Fjava\u002Fstreams-functional\u002Flambdas-functional-interfaces",1,{"subtopic":129,"path":130,"order":12},"Stream API","\u002Fjava\u002Fstreams-functional\u002Fstreams-api",{"subtopic":118,"path":21,"order":20},{"subtopic":133,"path":134,"order":135},"Optional","\u002Fjava\u002Fstreams-functional\u002Foptional",4,{"path":137,"title":138},"\u002Fblog\u002Fjava-collectors-groupingby","Java Collectors & groupingBy — collect(), Downstream Collectors & Custom Collectors",1782244116369]