StringBuilder
StringBuilder is a mutable sequence of characters. Unlike String, whose content can never change, a StringBuilder can be appended to, inserted into, edited, and shortened in place. It exists to build text efficiently, especially when the text is assembled piece by piece in a loop, and then converted to a String once at the end with toString().
Why StringBuilder Exists
Because strings are immutable, building text with += in a loop creates a new string on every iteration and copies everything accumulated so far:
String result = "";
for (int i = 0; i < 5; i++) {
result += i; // "0", "01", "012", "0123", "01234": five separate strings
}
For n pieces, the total copying grows roughly with n². For a few pieces nobody notices; for thousands, it becomes a real slowdown and a lot of garbage for the collector.
A StringBuilder keeps one internal character buffer and grows it only when needed:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append(i);
}
String result = sb.toString(); // "01234"
Each append writes into the existing buffer, so the total work grows linearly with the length of the result.
Creating a StringBuilder
StringBuilder a = new StringBuilder(); // empty, initial capacity 16
StringBuilder b = new StringBuilder("Hello"); // starts with text
StringBuilder c = new StringBuilder(1000); // empty, capacity 1000
Capacity is the size of the internal buffer; length is the number of characters actually stored. When the length would exceed the capacity, the buffer is reallocated larger (roughly doubling) and the content copied. If you know the approximate final size, passing it to the constructor avoids those reallocations.
Appending
append is overloaded for every primitive type, String, char[], CharSequence, and Object, and it returns the builder itself, so calls chain:
StringBuilder sb = new StringBuilder();
sb.append("Order #").append(1042)
.append(": ").append(3).append(" x ")
.append(499.5).append(' ')
.append(true);
System.out.println(sb); // Order #1042: 3 x 499.5 true
Appending null adds the four characters null, just as string concatenation does.
Editing Methods
| Method | Effect |
|---|---|
insert(index, value) | Insert at a position, shifting the rest right |
delete(start, end) | Remove characters from start up to (not including) end |
deleteCharAt(index) | Remove one character |
replace(start, end, str) | Replace a range with new text |
setCharAt(index, ch) | Overwrite one character |
reverse() | Reverse the sequence (keeps surrogate pairs intact) |
setLength(n) | Truncate to n characters, or pad with '\u0000' |
StringBuilder sb = new StringBuilder("Hello World");
sb.insert(5, ","); // "Hello, World"
sb.append('!'); // "Hello, World!"
sb.replace(7, 12, "Java"); // "Hello, Java!"
sb.deleteCharAt(sb.length() - 1).reverse(); // "avaJ ,olleH"
sb.setLength(0); // "" : quick way to reuse a builder
All of these modify the builder in place and return it. Indexes follow the same rules as String: zero-based, with exclusive end indexes, and StringIndexOutOfBoundsException outside the valid range.
Reading Methods
StringBuilder shares many read-only methods with String:
StringBuilder d = new StringBuilder("abc");
d.setCharAt(1, 'X'); // "aXc"
d.length(); // 3
d.charAt(0); // 'a'
d.indexOf("c"); // 2
d.substring(1); // "Xc" (returns a String)
d.isEmpty(); // false (Java 15+)
It implements CharSequence, so it can be passed to any method that accepts one, such as String.join, Pattern.matcher, or String.contentEquals.
Converting to a String
String text = sb.toString();
toString() copies the current content into a new, independent String. Later changes to the builder do not affect strings already produced.
Comparing Builders
StringBuilder does not override equals, so equals compares object identity:
StringBuilder a = new StringBuilder("x");
StringBuilder b = new StringBuilder("x");
a.equals(b); // false: different objects
a.compareTo(b) == 0; // true: compareTo (Java 11+) compares content
a.toString().equals(b.toString()); // true
"x".contentEquals(a); // true
For the same reason, do not use a StringBuilder as a HashMap key or put it in a HashSet: its content can change, and it does not define value equality.
When to Use StringBuilder
| Situation | Use |
|---|---|
| Building text inside a loop | StringBuilder |
| Assembling text across several methods (passing the builder along) | StringBuilder |
| Many inserts, deletes, or character edits | StringBuilder |
| A single expression joining a few values | + (the compiler optimizes it) |
| Joining a collection with a separator | String.join, StringJoiner, Collectors.joining |
| Fixed layout with padding and decimals | String.format (or formatted) |
StringBuilder is not thread-safe
StringBuilder has no synchronization. A single builder must not be modified by multiple threads at once. In practice this is rarely a limitation: builders are almost always local variables used by one thread. StringBuffer, covered in the next lesson, is the synchronized alternative.
Practical Example: Building an HTML Table
Generating HTML, CSV, SQL, or any structured text is a typical use: many small pieces appended in loops.
import java.util.*;
public class SbDemo {
record Row(String course, int enrolled, double rating) { }
static String escape(String s) {
StringBuilder sb = new StringBuilder(s.length());
for (char c : s.toCharArray()) {
switch (c) {
case '<' -> sb.append("<");
case '>' -> sb.append(">");
case '&' -> sb.append("&");
case '"' -> sb.append(""");
default -> sb.append(c);
}
}
return sb.toString();
}
static String htmlTable(List<Row> rows) {
StringBuilder html = new StringBuilder(256);
html.append("<table>\n <tr><th>Course</th><th>Enrolled</th><th>Rating</th></tr>\n");
for (Row r : rows) {
html.append(" <tr><td>").append(escape(r.course()))
.append("</td><td>").append(r.enrolled())
.append("</td><td>").append(r.rating())
.append("</td></tr>\n");
}
return html.append("</table>").toString();
}
public static void main(String[] args) {
System.out.println(htmlTable(List.of(
new Row("Java Basics", 1250, 4.7),
new Row("C & C++ <Advanced>", 430, 4.5))));
}
}
Output:
<table>
<tr><th>Course</th><th>Enrolled</th><th>Rating</th></tr>
<tr><td>Java Basics</td><td>1250</td><td>4.7</td></tr>
<tr><td>C & C++ <Advanced></td><td>430</td><td>4.5</td></tr>
</table>
Two builders do the work. escape walks through a course name and appends either the character itself or its HTML entity, so a title containing < or & cannot break the page or inject markup; sizing the builder with s.length() avoids most reallocations. htmlTable builds the whole table with chained append calls, appending numbers directly without converting them first, and converts to a String exactly once at the end. The same pattern, a builder plus an escaping function for untrusted values, applies when generating CSV, JSON, or XML by hand, although real projects usually use a template engine or library for HTML and JSON.