Join Strings Using Collectors.joining() in Java
Problem
Joining combines many strings into one, putting a separator between them and, optionally, a prefix before the first and a suffix after the last.
Given a list of tags, print them as a comma-separated line and as an upper-cased, bracketed list.
Java Program
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class JoinStringsUsingStreams {
public static void main(String[] args) {
List<String> tags = Arrays.asList("java", "streams", "collectors", "interview");
String plain = tags.stream().collect(Collectors.joining(", "));
String wrapped = tags.stream()
.map(String::toUpperCase)
.collect(Collectors.joining(" | ", "[", "]")); // separator, prefix, suffix
System.out.println(plain);
System.out.println(wrapped);
}
}Output
Core Logic
Collectors.joining is the stream way to build a delimited string, and it fits naturally after a map step.
- 1
joining(", ")puts a comma and a space between elements, with nothing at the ends. - 2
map(String::toUpperCase)transforms each tag before it's joined. - 3
joining(" | ", "[", "]")adds a separator, a prefix and a suffix.
[JAVA | STREAMS | COLLECTORS | INTERVIEW].Key Point: Joining never adds a trailing separator, which avoids the classic "remove the last comma" fix-up of manual loops.
Why: Every character of the result, of total length L, is copied once into a builder.
Key Concepts
Approach 2: String.join and StringJoiner
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;
public class JoinStringsStringJoin {
public static void main(String[] args) {
List<String> tags = Arrays.asList("java", "streams", "collectors", "interview");
System.out.println(String.join(", ", tags)); // simplest when no transformation is needed
StringJoiner joiner = new StringJoiner(" | ", "[", "]");
joiner.setEmptyValue("(no tags)"); // printed if nothing is added
for (String t : tags) joiner.add(t.toUpperCase());
System.out.println(joiner);
System.out.println(new StringJoiner(", ", "[", "]").setEmptyValue("(no tags)"));
}
}
Output
Core Logic
When no transformation is needed, String.join is simpler, and StringJoiner adds control such as a value for an empty result.
- 1
String.join(", ", tags)joins anyIterableof strings. - 2
StringJoinertakes the same separator, prefix and suffix as the collector. - 3
setEmptyValue("(no tags)")is returned when nothing was added.
(no tags) because that joiner never received an element.Key Point: Collectors.joining is built on StringJoiner, so all three produce identical text.
Why: Each character of the result is appended once.
Key Concepts
Interview Questions
Can Collectors.joining work on a stream of numbers?
Not directly — it needs CharSequence elements. Map the numbers first, for example map(String::valueOf).
Why not just use + in a loop?
Each + on a String creates a new object, so a loop is O(n²) in the worst case; joining uses one builder and stays linear.