Back to Stream: map & Transform
Q10Question 10 of 34

Extract the email addresses of all employees and return as a comma-separated string using Collectors.joining(", ")

Key Concepts
  • Collectors.joining(delimiter) concatenates a stream of strings into one string with the given delimiter
  • The three-argument joining(delimiter, prefix, suffix) wraps the result, useful for bracketed lists
  • joining() uses a StringJoiner internally, appending to one buffer for O(n) performance
  • joining only works on Stream<CharSequence>; map to strings first before collecting
  • A null element in the stream throws NullPointerException during concatenation
  • No delimiter is placed before the first element or after the last element

Definition

`Collectors.joining(", ")` glues a stream of strings into one comma-separated string.
  • Collectors.joining(delimiter) concatenates a stream of strings with the given separator between each pair.
  • Three overloads: no-arg (plain concat), one-arg (delimiter), three-arg (delimiter, prefix, suffix).
  • Internally uses StringJoiner, which appends to a single buffer for O(n) performance.
  • No trailing delimiter: the separator appears only between elements, not at the start or end.
  • Requires Stream: you must map to strings first.

The office manager at the National Bank of India wants to send one email to all staff, so she needs every address strung together, separated by commas, to paste into the To box. joining(", ") is exactly that stringing-together.

Understanding the Concepts

What does Collectors.joining do?

First you project each employee to its email with map(Employee::getEmail), producing a stream of email strings. Then the terminal collector Collectors.joining(", ") concatenates them into a single string, inserting the delimiter between consecutive elements but not at the very start or very end.

How do you extract emails as a comma-separated string?

JAVA
package com.bank.hr;

import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;

public class JoiningEmails {
    public static void main(String[] args) {
        List<Employee> staff = List.of(
            new Employee("Rajesh",  "rajesh@nbi.com",  "Loans",   new BigDecimal("95000")),
            new Employee("Priya",   "priya@nbi.com",   "IT",      new BigDecimal("120000")),
            new Employee("Amit",    "amit@nbi.com",    "Finance", new BigDecimal("85000")));

        String emailList = staff.stream()
            .map(Employee::getEmail)
            .collect(Collectors.joining(", "));

        System.out.println(emailList);
    }
}
TEXT
rajesh@nbi.com, priya@nbi.com, amit@nbi.com

What are the three overloads of joining?

| Overload | Example | Result |
|---|---|---|
| joining() | joining() | "rajesh@nbi.compriya@nbi.comamit@nbi.com" |
| joining(delimiter) | joining(", ") | "rajesh@nbi.com, priya@nbi.com, amit@nbi.com" |
| joining(delim, prefix, suffix) | joining(", ", "[", "]") | "[rajesh@nbi.com, priya@nbi.com, amit@nbi.com]" |

JAVA
// Bracketed list for a summary report
String bracketed = staff.stream()
    .map(Employee::getEmail)
    .collect(Collectors.joining(", ", "[", "]"));

System.out.println(bracketed);
// [rajesh@nbi.com, priya@nbi.com, amit@nbi.com]

What are common mistakes?

Mistake: a null email in the stream throws NullPointerException during concatenation.

JAVA
// WRONG: if any email is null, joining crashes
String emails = staff.stream()
    .map(Employee::getEmail)  // might return null
    .collect(Collectors.joining(", ")); // NPE!

// CORRECT: filter out nulls first
String emails = staff.stream()
    .map(Employee::getEmail)
    .filter(java.util.Objects::nonNull)
    .collect(Collectors.joining(", "));

How is joining different from reduce for string concatenation?

DIAGRAM
| Approach | Performance | Simplicity |
|---|---|---|
| Collectors.joining(", ") | O(n) via StringJoiner | One line, clean |
| reduce("", (a, b) -> a + ", " + b) | O(n^2) via String copies | Bug-prone (leading comma) |
JAVA
// Joining: O(n), clean, no leading-comma bug
staff.stream().map(Employee::getEmail)
    .collect(Collectors.joining(", "));

// Reduce: O(n^2), must guard leading comma manually
staff.stream().map(Employee::getEmail)
    .reduce("", (a, b) -> a.isEmpty() ? b : a + ", " + b);

What about joining non-string types?

joinig only works on Stream. For non-string types, map to a string representation first.

JAVA
// Joining salary amounts as a string
String salaries = staff.stream()
    .map(e -> e.getSalary().toPlainString())
    .collect(Collectors.joining(" | "));

System.out.println(salaries);
// 95000 | 120000 | 85000

What should a senior engineer know?

  • Under the hood, joining uses a StringJoiner, which is efficient because it appends to a single growing buffer rather than creating a new string for each concatenation.
  • For very large datasets, the resulting string could be huge; consider streaming output instead of building one giant string.
  • joining is the idiomatic and efficient way to build delimited strings; never use reduce with string concatenation in production.

What are common interview follow-up questions?

  • "What happens if the stream is empty?" joining returns an empty string (or just prefix+suffix for the three-arg form).
  • "Why not use reduce for string concatenation?" It is O(n^2) because each + creates a new String.
  • "Can you add a prefix and suffix?" Yes, use the three-argument joining(delimiter, prefix, suffix).

Visual Overview

DIAGRAM
  Stream<Employee>
  [Rajesh, Priya, Amit]
       │
       │  .map(Employee::getEmail)
       ▼
  Stream<String>
  ["rajesh@nbi.com", "priya@nbi.com", "amit@nbi.com"]
       │
       │  .collect(Collectors.joining(", "))
       │
       │  StringJoiner buffer:
       │  "rajesh@nbi.com"
       │  "rajesh@nbi.com, priya@nbi.com"
       │  "rajesh@nbi.com, priya@nbi.com, amit@nbi.com"
       ▼
  String: "rajesh@nbi.com, priya@nbi.com, amit@nbi.com"

Key Takeaway

To turn a stream of strings into one delimited string I use the joining collector, giving it the delimiter I want between elements. Here I first mapped each employee to its email, then collected with joining(", "), which concatenates all the addresses with a comma and space between each, and no delimiter before the first or after the last.

In my banking project at the National Bank of India this built the recipient line for a staff circular, where the office manager needed every address in one comma-separated string. The three-argument form with prefix and suffix is handy for building a bracketed summary.

joining uses a StringJoiner internally, so it appends to one growing buffer for O(n) performance, unlike reduce with string concatenation which is O(n^2). A null in the stream throws NullPointerException, so I always filter nulls before joining.

Remember one sentence: Collectors.joining(", ") concatenates a stream of strings efficiently using a StringJoiner, with the delimiter only between elements.

---

Share
Use ← → arrow keys to navigate