Collectors.joining()
Concatenates stream elements into one String, with an optional delimiter, prefix and suffix.
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)- Added in Java Java 8
Parameters
| Parameter | Type | Description |
|---|---|---|
$delimiteroptional | CharSequence= "" (joining() has no delimiter) | Placed between consecutive elements. |
$prefixoptional | CharSequence= "" | Placed at the start of the result. Only in the three-argument form. |
$suffixoptional | CharSequence= "" | Placed at the end of the result. Only in the three-argument form. |
Return value
Collector<CharSequence, ?, String>A collector that concatenates the elements in encounter order, separated by the delimiter and wrapped in the prefix and suffix. An empty stream gives prefix + suffix, or an empty string with no prefix or suffix.
Errors
A null delimiter, prefix or suffix is not rejected when the collector is built; collect then throws NullPointerException, even on an empty stream. A null stream element does not throw; it is appended as the text "null".
Collectors.joining concatenates the elements of a stream into one String. There are three forms:
joining(): elements back to back, no separator.joining(", "): a delimiter between elements, not after the last one.joining(", ", "[", "]"): a delimiter plus a prefix and suffix around the whole result.
It collects the parts and builds the string once at the end, so it is the right tool instead of += in a loop, and it never leaves a trailing comma to trim off.
It only accepts text
The collector's element type is CharSequence. A Stream<Integer> or a stream of records does not compile with joining; map the elements to strings first with map(String::valueOf) or a field accessor. That compile error is the most common question about this method.
Empty streams and nulls
An empty stream gives an empty string, or just the prefix and suffix: "[]" for joining(", ", "[", "]"). That is usually what you want for a list display, but it can produce broken output such as WHERE id IN (), which is a SQL syntax error, so check for an empty input before building a query. A null element is not an error; it is appended as the four letters null, which tends to show up in user-facing text. Filter nulls out or map them to a default first.
Back-end uses and limits
Typical jobs are log lines, header values such as Allow: GET, POST, CSV rows and SQL placeholder lists like (?, ?, ?). Join placeholders, never the values themselves: values go to PreparedStatement parameters. For CSV and JSON, joining only puts the separators in; quoting and escaping are your job, which is why a JSON library is the better choice once the data is more than a few known-safe strings.
When you already have a List<String>, String.join(", ", list) is shorter. joining wins in the middle of a pipeline, after filter, map or sorted, or as a downstream collector inside groupingBy.
Example
When to use it
- Building the value of an Allow header for a 405 Method Not Allowed response.
- Generating '(?, ?, ?)' placeholder lists for a SQL IN clause whose values are bound with PreparedStatement.
- Writing one log line that lists the ids of every record a batch job skipped.
Frequently asked questions
How do I join a list of integers into a comma-separated string?
Map each number to a String first, because joining only accepts CharSequence: ids.stream().map(String::valueOf).collect(Collectors.joining(",")).
What does Collectors.joining return for an empty stream?
An empty string for joining() and joining(delimiter). The three-argument form returns the prefix followed by the suffix, so joining(", ", "[", "]") gives "[]".
Should I use String.join or Collectors.joining?
Use String.join when you already have the strings in a collection or array. Use Collectors.joining inside a stream pipeline, after filter, map or sorted, or as a downstream collector of groupingBy.
More examples
Joining numbers and null elements
map(String::valueOf) converts each number to text so joining accepts it. Nulls are joined as the word null, so filter them out before the text reaches a user or an email template.
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
void main() {
List<Integer> ids = List.of(101, 102, 103);
// ids.stream().collect(Collectors.joining(",")) does not compile: Integer is not a CharSequence.
String idList = ids.stream().map(String::valueOf).collect(Collectors.joining(","));
IO.println(idList);
List<String> names = Arrays.asList("ada", null, "cy");
IO.println(names.stream().collect(Collectors.joining(", ")));
IO.println(names.stream().filter(Objects::nonNull).collect(Collectors.joining(", ")));
}
// Output:
// 101,102,103
// ada, null, cy
// ada, cyA CSV line with quoted fields
joining only places the commas; quoting a field that contains a comma or a quote, and doubling inner quotes, is done in the map step. Skipping that step shifts every column after the first name with a comma in it.
import java.util.List;
import java.util.stream.Collectors;
void main() {
List<String> fields = List.of("o-17", "Smith, Ada", "say \"hi\"", "2500");
String line = fields.stream()
.map(f -> f.contains(",") || f.contains("\"")
? "\"" + f.replace("\"", "\"\"") + "\""
: f)
.collect(Collectors.joining(","));
IO.println(line);
}
// Output:
// o-17,"Smith, Ada","say ""hi""",2500An Allow header from the supported methods
Collectors.joining fits after a filter and map in the same pipeline. String.join is enough when you already hold exactly the strings you want, as with the key set on the last line.
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
void main() {
Map<String, Boolean> enabled = new TreeMap<>(Map.of(
"GET", true, "POST", true, "PUT", false, "DELETE", true));
String allow = enabled.entrySet().stream()
.filter(Map.Entry::getValue)
.map(Map.Entry::getKey)
.collect(Collectors.joining(", "));
IO.println("Allow: " + allow);
IO.println("Allow: " + String.join(", ", enabled.keySet()));
}
// Output:
// Allow: DELETE, GET, POST
// Allow: DELETE, GET, POST, PUT
Discussion