StringBuilder
When you need to build or modify a string in a loop or across multiple steps, StringBuilder is your go-to tool. It gives you a mutable, resizable character buffer without the allocation overhead of repeatedly creating new String objects — and without the synchronization cost of StringBuffer.
Why StringBuilder Exists
Java strings are immutable. Every time you concatenate or modify a String, you get a brand-new object:
String result = "";
for (int i = 0; i < 5; i++) {
result += i; // creates a new String object on every iteration
}
System.out.println(result);
Output:
01234
That loop silently allocates five intermediate String objects that are immediately discarded. At small scale you won’t notice, but in a loop with thousands of iterations those short-lived objects pile up and strain the garbage collector.
StringBuilder solves this by keeping a single, resizable char buffer that you mutate in place:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append(i); // mutates the same object — zero extra allocations
}
System.out.println(sb.toString());
Output:
01234
Note: The Java compiler already converts simple
+concatenations of literals at compile time. But for concatenation inside a loop, modern compilers (Java 9+) may still produce suboptimal bytecode. Explicitly usingStringBuilderkeeps your intent clear and guarantees efficiency.
Creating a StringBuilder
// 1. Empty buffer — default capacity is 16 characters
StringBuilder sb1 = new StringBuilder();
// 2. Pre-populated — capacity = string.length() + 16
StringBuilder sb2 = new StringBuilder("Hello");
// 3. Specific initial capacity — avoids early resizing
StringBuilder sb3 = new StringBuilder(256);
Tip: If you know the rough final length, pass it as the initial capacity. Skipping unnecessary resize-and-copy cycles gives a measurable speed boost for large strings.
Commonly Used Methods
append()
The most-used method. It adds content to the end of the buffer and returns this, so you can chain calls:
StringBuilder sb = new StringBuilder();
sb.append("Name: ")
.append("Alice")
.append(", Age: ")
.append(30);
System.out.println(sb.toString());
Output:
Name: Alice, Age: 30
append() is overloaded for String, int, long, float, double, char, boolean, char[], CharSequence, and Object.
insert()
Inserts content at any position in the buffer:
StringBuilder sb = new StringBuilder("Hello World");
sb.insert(5, ",");
System.out.println(sb);
Output:
Hello, World
replace()
Replaces the characters in the half-open range [start, end):
StringBuilder sb = new StringBuilder("Hello Java");
sb.replace(6, 10, "World");
System.out.println(sb);
Output:
Hello World
delete() and deleteCharAt()
StringBuilder sb = new StringBuilder("Hello, World!");
sb.delete(5, 7); // removes ", "
System.out.println(sb);
sb.deleteCharAt(5); // removes the space
System.out.println(sb);
Output:
Hello World!
HelloWorld!
reverse()
Reverses the entire sequence in place — perfect for the classic reverse a string problem:
StringBuilder sb = new StringBuilder("abcde");
sb.reverse();
System.out.println(sb);
Output:
edcba
indexOf() and lastIndexOf()
StringBuilder sb = new StringBuilder("banana");
System.out.println(sb.indexOf("an")); // 1
System.out.println(sb.lastIndexOf("an")); // 3
substring()
Returns a new String extracted from the buffer — the buffer itself is unchanged:
StringBuilder sb = new StringBuilder("Hello, World!");
System.out.println(sb.substring(7)); // World!
System.out.println(sb.substring(7, 12)); // World
length() and capacity()
StringBuilder sb = new StringBuilder("Hello");
System.out.println(sb.length()); // 5 — chars currently stored
System.out.println(sb.capacity()); // 21 — internal array size (5 + 16)
charAt() and setCharAt()
StringBuilder sb = new StringBuilder("Hello");
System.out.println(sb.charAt(1)); // e
sb.setCharAt(0, 'h');
System.out.println(sb); // hello
Practical Example: Building a Query String
public class QueryBuilder {
public static String build(String base, String[][] params) {
StringBuilder sb = new StringBuilder(base);
sb.append('?');
for (int i = 0; i < params.length; i++) {
sb.append(params[i][0])
.append('=')
.append(params[i][1]);
if (i < params.length - 1) {
sb.append('&');
}
}
return sb.toString();
}
public static void main(String[] args) {
String[][] params = {{"page", "1"}, {"sort", "asc"}, {"q", "java"}};
System.out.println(build("https://example.com/search", params));
}
}
Output:
https://example.com/search?page=1&sort=asc&q=java
Quick Reference: Method Table
| Method | Description |
|---|---|
append(x) | Appends x to the end |
insert(offset, x) | Inserts x at offset |
replace(start, end, str) | Replaces [start, end) with str |
delete(start, end) | Removes chars at [start, end) |
deleteCharAt(index) | Removes single char at index |
reverse() | Reverses the sequence |
indexOf(str) | First occurrence of str |
lastIndexOf(str) | Last occurrence of str |
substring(start) | String from start to end |
substring(start, end) | String from start to end |
charAt(index) | Char at index |
setCharAt(index, ch) | Sets char at index to ch |
length() | Current character count |
capacity() | Current internal array size |
ensureCapacity(min) | Guarantees at least min capacity |
trimToSize() | Shrinks buffer to fit current length |
toString() | Returns a String snapshot |
StringBuilder vs StringBuffer
Both classes share almost the same API and even the same parent class (AbstractStringBuilder). The one key difference is thread safety:
| Feature | StringBuilder | StringBuffer |
|---|---|---|
| Thread-safe | No | Yes (synchronized) |
| Performance | Faster | Slightly slower |
| Introduced in | Java 5 | Java 1.0 |
| Use case | Single-threaded code | Shared across threads |
Tip: In everyday single-threaded code — which covers the vast majority of string-building tasks — always prefer
StringBuilder. Only switch to StringBuffer when multiple threads genuinely share the same buffer instance.
Under the Hood
StringBuilder and StringBuffer both extend AbstractStringBuilder, which holds the actual storage. In Java 8 and earlier that storage is a char[]. From Java 9 onwards (with the Compact Strings optimisation, JEP 254), the JVM stores Latin-1 strings as a byte[] with one byte per character instead of two, halving memory for ASCII-only content. A coder byte tracks whether the buffer is Latin-1 or UTF-16.
Capacity growth: When an append would overflow the current array, AbstractStringBuilder allocates a new array of size (oldCapacity × 2) + 2 and copies the existing data across. This doubling strategy gives O(1) amortised append performance — the same idea as ArrayList growth. You pay for a copy occasionally, but the average cost per character stays constant.
Because StringBuilder methods are not synchronized, the JIT compiler is free to inline and optimise them aggressively. In tight loops the difference versus StringBuffer can be significant. If you look at the generated bytecode with the javap tool, you’ll see that method calls on StringBuilder are often inlined completely away.
Warning:
StringBuilderis not thread-safe. If two threads callappend()concurrently on the same instance, the internalcountfield andvaluearray can become inconsistent, producing garbled output or anArrayIndexOutOfBoundsException. Use StringBuffer or aThreadLocal<StringBuilder>if you need shared mutable string building.
Converting Between Types
// StringBuilder -> String
StringBuilder sb = new StringBuilder("Hello");
String s = sb.toString();
// String -> StringBuilder
String original = "World";
StringBuilder sb2 = new StringBuilder(original);
// StringBuilder -> StringBuffer (rare, but possible)
StringBuffer sbf = new StringBuffer(sb.toString());
Note:
toString()creates a newStringobject that shares the underlyingbyte[](orchar[]) with the builder until either object is modified — a copy-on-write optimisation in some JVM implementations. This makes the conversion essentially free in the common case.
Related Topics
- StringBuffer — the synchronized sibling; use when multiple threads share a buffer
- StringBuffer vs StringBuilder — a focused side-by-side comparison to help you choose
- String vs StringBuffer — when sticking with immutable
Stringis the right call - String Immutability — the reason mutable builders exist in the first place
- String Methods — the full API of the immutable
Stringclass - Garbage Collection — understanding the GC pressure that
StringBuilderhelps you avoid