Back to Stream: map & Transform
Q30Question 30 of 34

Convert a list of strings to a single comma-separated string using Collectors.joining

Key Concepts
  • Collectors.joining(delimiter) concatenates stream elements with the delimiter placed only between them
  • Three-argument joining(delimiter, prefix, suffix) wraps the result, producing formats like [a, b, c]
  • joining operates on Stream<CharSequence>, so non-string elements must be mapped to String first
  • Internally uses StringBuilder, so it is efficient even for large streams
  • Empty stream yields empty string or just prefix+suffix with the three-argument form
  • Replaces error-prone manual loops with separator flags or trailing-comma trimming

Definition

Collectors.joining(", ") concatenates stream elements into one comma-separated string, placing the delimiter only between elements.
  • joining(delimiter) inserts the delimiter between elements, never before the first or after the last.
  • joining(delimiter, prefix, suffix) additionally wraps the result in a prefix and suffix.
  • No-argument joining() concatenates with no separator at all.
  • Requires CharSequence elements, so non-string types need map(String::valueOf) first.
  • Uses StringBuilder internally, making it efficient even for many elements.

Priya at the National Bank of India has a list of account holder names and needs them on a single summary-header line separated by commas. She glues them together with a comma and space between each, producing a clean line like Rajesh, Priya, Amit with no stray commas.

Understanding the Concepts

What does this question ask?

The interviewer wants you to join a list of strings into one delimited string. This tests Collectors.joining, its overloaded forms, and handling of non-string elements.

How do you join strings with a comma delimiter?

JAVA
package com.bank.service;

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

public class JoiningDemo {
    public static void main(String[] args) {
        List<String> holders = List.of("Rajesh", "Priya", "Amit", "Deepak");

        String result = holders.stream()
            .collect(Collectors.joining(", "));

        System.out.println(result);
    }
}
TEXT
Rajesh, Priya, Amit, Deepak

The delimiter appears only between elements, never at the start or end.

How does the three-argument form work?

JAVA
String bracketed = holders.stream()
    .collect(Collectors.joining(", ", "[", "]"));

System.out.println(bracketed);
TEXT
[Rajesh, Priya, Amit, Deepak]

The prefix and suffix wrap the joined result, which is useful for producing JSON-like or display formats.

How do you join non-string elements?

joining requires Stream. For other types, map to String first.

JAVA
package com.bank.service;

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

public class JoiningNonString {
    public static void main(String[] args) {
        List<BigDecimal> balances = List.of(
            new BigDecimal("50000.00"),
            new BigDecimal("125000.50"),
            new BigDecimal("75000.25"));

        String csv = balances.stream()
            .map(BigDecimal::toPlainString)
            .collect(Collectors.joining(", "));

        System.out.println("Balances: " + csv);
    }
}
TEXT
Balances: 50000.00, 125000.50, 75000.25

What happens with an empty stream?

JAVA
List<String> empty = List.of();

String noArgs = empty.stream().collect(Collectors.joining());
String withDelim = empty.stream().collect(Collectors.joining(", "));
String withWrap = empty.stream().collect(Collectors.joining(", ", "[", "]"));

System.out.println("No args: '" + noArgs + "'");
System.out.println("Delim: '" + withDelim + "'");
System.out.println("Wrap: '" + withWrap + "'");
TEXT
No args: ''
Delim: ''
Wrap: '[]'

The one- and two-argument forms return an empty string. The three-argument form returns just the prefix and suffix.

How is joining different from String.join?

| Feature | Collectors.joining | String.join |
|---|---|---|
| Works with | Stream pipeline | Array or Iterable directly |
| Prefix/suffix | Yes (three-argument form) | No |
| Pipeline integration | Yes, as a terminal collector | No, standalone static method |

JAVA
// String.join: standalone, no prefix/suffix
String sj = String.join(", ", holders);

// Collectors.joining: inside a pipeline with map/filter
String cj = holders.stream()
    .filter(name -> name.length() > 4)
    .collect(Collectors.joining(", "));

What are common mistakes?

Mistake 1: using manual loops with separator flags.

JAVA
// WRONG: error-prone manual loop
List<String> names = List.of("Rajesh", "Priya");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < names.size(); i++) {
    if (i > 0) sb.append(", ");
    sb.append(names.get(i));
}
// Works but verbose and easy to get wrong

// CORRECT: joining handles it
String result = names.stream().collect(Collectors.joining(", "));

Mistake 2: assuming joining works on non-CharSequence streams.

JAVA
// WRONG: compile error, Integer is not CharSequence
// List.of(1, 2, 3).stream().collect(Collectors.joining(", "));

// CORRECT: map to String first
String nums = List.of(1, 2, 3).stream()
    .map(String::valueOf)
    .collect(Collectors.joining(", "));

What about CSV output with escaping?

Plain joining does not handle values that contain the delimiter character. For proper CSV encoding, you need to escape or quote such values.

JAVA
List<String> values = List.of("Rajesh, Kumar", "Priya", "Amit");

// WRONG: produces ambiguous CSV
String bad = values.stream().collect(Collectors.joining(","));
// "Rajesh, Kumar,Priya,Amit" -> 4 fields instead of 3

// CORRECT: quote values containing the delimiter
String good = values.stream()
    .map(v -> v.contains(",") ? "\"" + v + "\"" : v)
    .collect(Collectors.joining(","));
// "\"Rajesh, Kumar\",Priya,Amit" -> 3 fields

What are common interview follow-up questions?

  • "How do you split a comma-separated string back into a list?" Use Arrays.asList(s.split(", ")) or Pattern.compile(", ").splitAsStream(s).
  • "How do you join with a newline?" Use joining("\n") for line-separated output.
  • "How do you build a SQL IN clause?" Use joining(", ", "IN (", ")") after mapping values to quoted strings.

Visual Overview

DIAGRAM
  List: ["Rajesh", "Priya", "Amit", "Deepak"]
       │
       │  .stream()
       │  .collect(Collectors.joining(", "))
       ▼
  ┌───────────────────────────────────────┐
  │  "Rajesh" + ", " +                   │
  │  "Priya"  + ", " +                   │
  │  "Amit"   + ", " +                   │
  │  "Deepak"                            │
  └───────────────────────────────────────┘
       │
       ▼
  "Rajesh, Priya, Amit, Deepak"

  Three-argument: joining(", ", "[", "]")
       ▼
  "[Rajesh, Priya, Amit, Deepak]"

Key Takeaway

Collectors.joining is the idiomatic way to turn a list of strings into a single delimited line. The one-argument form inserts the delimiter only between elements, never before the first or after the last. The three-argument form additionally wraps the result with a prefix and suffix, which is useful for bracketed or parenthesized output.

Since joining operates on a Stream of CharSequence, non-string elements like BigDecimal balances must be mapped to String first. Internally it uses a StringBuilder, so it is efficient even for large collections. At the National Bank of India, Rajesh used this to render account holder names as a comma-separated summary header.

The senior point is that joining replaces error-prone manual loops with separator flags or trailing-comma trimming. It also handles the empty-stream edge case correctly: an empty stream produces an empty string, or just the prefix and suffix with the three-argument form. For CSV output where values might contain the delimiter, joining alone is not enough and you need quoting or escaping.

Remember one sentence: Collectors.joining inserts the delimiter only between elements, uses StringBuilder internally, and replaces manual separator logic.

---

Share
Use ← → arrow keys to navigate