Java
String Building
Programming
Delimited Items
Coding Techniques

What's the best way to build a string of delimited items in Java?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Building a delimited string in Java is a small task that appears everywhere: SQL fragments, log messages, CSV-like output, headers, and UI labels. The best solution depends on whether you already have strings, need to transform values first, or care about null handling and performance details. In modern Java, the cleanest answers are usually String.join, StringJoiner, or Collectors.joining.

Use String.join When You Already Have Strings

If you already have a collection of strings, String.join is the most direct option.

java
1import java.util.List;
2
3public class Demo {
4    public static void main(String[] args) {
5        List<String> values = List.of("red", "green", "blue");
6        String result = String.join(", ", values);
7        System.out.println(result);
8    }
9}

This is concise, readable, and avoids the classic mistake of appending an extra delimiter and trimming it afterward.

It is usually the best choice when:

  • the elements are already strings
  • you do not need a prefix or suffix
  • you are not transforming values during the join

Use Streams When You Need Transformation

If your items are not strings yet, streams can map them before joining.

java
1import java.util.List;
2import java.util.stream.Collectors;
3
4record User(int id, String name) {}
5
6public class Demo {
7    public static void main(String[] args) {
8        List<User> users = List.of(
9            new User(1, "Alice"),
10            new User(2, "Bob"),
11            new User(3, "Carla")
12        );
13
14        String result = users.stream()
15            .map(User::name)
16            .collect(Collectors.joining(" | "));
17
18        System.out.println(result);
19    }
20}

That pattern is ideal when you need formatting, filtering, or extraction before concatenation.

Use StringJoiner for Incremental Construction

When items arrive one at a time, StringJoiner is a good fit.

java
1import java.util.StringJoiner;
2
3public class Demo {
4    public static void main(String[] args) {
5        StringJoiner joiner = new StringJoiner(", ", "[", "]");
6        joiner.add("apple");
7        joiner.add("banana");
8        joiner.add("cherry");
9
10        System.out.println(joiner);
11    }
12}

Unlike a raw StringBuilder, StringJoiner understands delimiter placement and can also add a prefix and suffix cleanly.

StringBuilder Is Still Useful for Custom Rules

There are still cases where a manual loop with StringBuilder is the best tool, especially when joining logic is conditional or intertwined with other formatting decisions.

java
1import java.util.List;
2
3public class Demo {
4    public static void main(String[] args) {
5        List<Integer> numbers = List.of(1, 2, 3, 4, 5);
6        StringBuilder builder = new StringBuilder();
7
8        for (int number : numbers) {
9            if (number % 2 == 0) {
10                if (builder.length() > 0) {
11                    builder.append(", ");
12                }
13                builder.append(number);
14            }
15        }
16
17        System.out.println(builder);
18    }
19}

The key idea is that StringBuilder should be used because the logic is custom, not because you think every join operation needs low-level manual control.

Watch Out for Null Values

Null handling is where many joining implementations become inconsistent. String.join and stream-based joining can throw a NullPointerException depending on how you feed them data.

If nulls are possible, normalize them first:

java
1import java.util.Arrays;
2import java.util.List;
3import java.util.Objects;
4import java.util.stream.Collectors;
5
6public class Demo {
7    public static void main(String[] args) {
8        List<String> raw = Arrays.asList("A", null, "C");
9
10        String result = raw.stream()
11            .filter(Objects::nonNull)
12            .collect(Collectors.joining(", "));
13
14        System.out.println(result);
15    }
16}

That makes the intended policy clear instead of letting null behavior become an accident.

Avoid the Old “Trim the Last Delimiter” Pattern

Older Java examples often append a delimiter after every item and then remove the final delimiter at the end. It works, but it is brittle and unnecessary in modern Java.

This style is error-prone:

java
builder.append(item).append(", ");
builder.setLength(builder.length() - 2);

If the collection is empty, that code can break. Built-in joining APIs avoid that edge case entirely.

Common Pitfalls

The most common mistake is using manual delimiter trimming when String.join, StringJoiner, or Collectors.joining would be simpler and safer.

Another issue is choosing a stream when the data is already a list of strings and no transformation is needed. Streams are fine, but String.join is clearer in that case.

Null handling is also a frequent source of bugs. Decide whether nulls should be skipped, replaced, or rejected instead of letting the join fail unexpectedly.

Finally, do not over-optimize prematurely. For ordinary application code, readability matters more than micro-benchmarks.

Summary

  • Use String.join when you already have strings and just need a delimiter.
  • Use Collectors.joining when you need to map or filter values first.
  • Use StringJoiner for incremental building or when prefix and suffix matter.
  • Use StringBuilder only when the joining rules are more custom than the standard APIs support.
  • Avoid manual “remove the last delimiter” logic in modern Java code.

Course illustration
Course illustration

All Rights Reserved.