String.join()

Joins strings from varargs or any Iterable with a delimiter between them, with no trailing separator to trim off.

Signature
public static String join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
  • Added in Java Java 8

Parameters

ParameterTypeDescription
$delimiterCharSequenceText placed between each pair of elements, not before the first or after the last.
$elementsIterable<? extends CharSequence>The values to join. The overload join(CharSequence, CharSequence...) takes varargs or an array instead.

Return value

StringA new String of the elements separated by the delimiter. An empty Iterable gives an empty String.

Errors

NullPointerException if delimiter or elements is null. A null element is joined as the text "null".

String.join(delimiter, elements) puts the delimiter between each pair of elements and returns one String. It is a static method with two overloads: one takes varargs (so also a String[]), the other takes any Iterable, such as a List or Set.

String.join(", ", "GET", "POST")         // "GET, POST"
String.join("/", List.of("api", "v2"))     // "api/v2"

Why not a loop?

A loop that appends each value and then a comma leaves a trailing comma that needs to be cut off, and an off-by-one in that fix is a classic bug. String.join handles the first, last and only element for you. An empty collection gives an empty String; a single element gives that element with no delimiter.

Choosing between the join tools

  • String.join: you already have a collection or array of Strings.
  • Collectors.joining(delim, prefix, suffix): the values come from a stream, often after map or filter, or you need brackets around the result.
  • StringJoiner: you add values one at a time in a loop with conditions, or want a custom text for the empty case via setEmptyValue.

All three use a StringBuilder-style buffer internally, so they are efficient for large inputs.

Gotchas

Elements must be CharSequences. A List<Integer> does not compile; map it to Strings first, for example with a stream. A null element is not skipped: it appears as the four letters null. Filter nulls and blanks before joining.

Joining is not escaping. If a value can contain the delimiter, the result cannot be split back reliably, which matters for CSV lines, cache keys and log fields. For SQL, join only placeholders such as ?, never values; build the IN (?, ?, ?) list with Collections.nCopies and bind the values on a PreparedStatement.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Building the Allow header for a 405 response from the methods a route supports.
  • Generating an IN (?, ?, ?) list for a PreparedStatement with one placeholder per id.
  • Writing a list of tags or roles into one comma-separated column or log field.

Frequently asked questions

How do I join a List of Strings with commas in Java?

String.join(",", list). It works with any Iterable of CharSequence and puts the comma only between elements.

How do I join a List of Integers?

String.join needs CharSequence elements, so convert first: list.stream().map(String::valueOf).collect(Collectors.joining(",")).

What is the difference between String.join and Collectors.joining?

String.join works on an existing array or Iterable. Collectors.joining is a stream collector, so it fits after map and filter, and it can add a prefix and suffix.

More examples

Building an Allow header

An EnumSet iterates in declaration order whatever order you add to it, so the header is stable. A 405 Method Not Allowed response must carry this header.

Example · java
enum Method { GET, POST, PUT, DELETE }

void main() {
    Set<Method> allowed = EnumSet.of(Method.DELETE, Method.GET, Method.POST);
    List<String> names = allowed.stream().map(Method::name).toList();
    IO.println("Allow: " + String.join(", ", names));
}
// Output:
// Allow: GET, POST, DELETE

SQL placeholders, JSON arrays and StringJoiner

Only the question marks are joined into the SQL; the ids are bound later on a PreparedStatement. Collectors.joining adds a prefix and suffix, and StringJoiner can print a chosen value when nothing was added.

Example · java
void main() {
    List<Integer> ids = List.of(3, 7, 42);
    String placeholders = String.join(", ", Collections.nCopies(ids.size(), "?"));
    IO.println("SELECT * FROM orders WHERE id IN (" + placeholders + ")");

    String json = ids.stream().map(String::valueOf).collect(Collectors.joining(",", "[", "]"));
    IO.println(json);

    StringJoiner sj = new StringJoiner(", ", "{", "}");
    sj.setEmptyValue("none");
    IO.println(sj);
}
// Output:
// SELECT * FROM orders WHERE id IN (?, ?, ?)
// [3,7,42]
// none

Replacing a trailing-comma loop

The loop leaves a trailing comma, while join places the delimiter only between elements. Filtering blanks first avoids empty fields such as "java,,api".

Example · java
void main() {
    List<String> tags = List.of("java", "api", "backend");
    StringBuilder sb = new StringBuilder();
    for (String t : tags) sb.append(t).append(",");
    IO.println(sb);
    IO.println(String.join(",", tags));

    List<String> withBlanks = List.of("java", "", "api", " ");
    IO.println(String.join(",", withBlanks.stream().filter(s -> !s.isBlank()).toList()));
}
// Output:
// java,api,backend,
// java,api,backend
// java,api

Discussion

  • Be the first to comment on this lesson.