StringBuffer
When you need to build or modify a string repeatedly — especially in a multi-threaded environment — StringBuffer is your tool. Unlike a regular String, which is immutable (every “change” creates a new object), a StringBuffer lets you mutate the same object in place without generating a pile of intermediate strings.
Why Not Just Use String?
Every time you modify a String, Java creates a brand-new object on the heap:
String s = "Hello";
s += ", World"; // a new String object is allocated
s += "!"; // another new String object
For a handful of concatenations that is fine. But inside a loop with thousands of iterations, you can create thousands of throwaway String objects, putting real pressure on the garbage collector. StringBuffer solves this by maintaining a resizable internal char[] buffer and mutating it directly.
Note: Java’s String immutability is intentional and valuable (caching, security, thread safety for literals).
StringBufferis a complement, not a replacement.
Creating a StringBuffer
// 1. Empty buffer with default capacity (16 chars)
StringBuffer sb1 = new StringBuffer();
// 2. Start with an initial string (capacity = string length + 16)
StringBuffer sb2 = new StringBuffer("Hello");
// 3. Specify an initial capacity
StringBuffer sb3 = new StringBuffer(64);
Tip: If you know roughly how large your final string will be, pass that estimate to the constructor. This avoids costly array-resize operations.
Commonly Used Methods
append()
The workhorse method. It appends almost anything — strings, numbers, booleans, characters, even other objects — to the end of the buffer.
StringBuffer sb = new StringBuffer("Hello");
sb.append(", ");
sb.append("World");
sb.append('!');
System.out.println(sb.toString());
Output:
Hello, World!
append() is overloaded to accept String, int, long, float, double, char, boolean, char[], CharSequence, and Object.
insert()
Inserts content at a specific index:
StringBuffer sb = new StringBuffer("Hello World");
sb.insert(5, ",");
System.out.println(sb);
Output:
Hello, World
replace()
Replaces characters in the range [start, end):
StringBuffer sb = new StringBuffer("Hello Java");
sb.replace(6, 10, "World");
System.out.println(sb);
Output:
Hello World
delete() and deleteCharAt()
StringBuffer sb = new StringBuffer("Hello, World!");
sb.delete(5, 7); // removes ", "
System.out.println(sb); // Hello World!
sb.deleteCharAt(5); // removes space
System.out.println(sb); // HelloWorld!
Output:
Hello World!
HelloWorld!
reverse()
Reverses the entire sequence in place — a one-liner for the classic reverse a string problem:
StringBuffer sb = new StringBuffer("abcde");
sb.reverse();
System.out.println(sb);
Output:
edcba
indexOf() and lastIndexOf()
StringBuffer sb = new StringBuffer("banana");
System.out.println(sb.indexOf("an")); // 1
System.out.println(sb.lastIndexOf("an")); // 3
substring()
Returns a new String (not a StringBuffer) from the buffer:
StringBuffer sb = new StringBuffer("Hello, World!");
System.out.println(sb.substring(7)); // World!
System.out.println(sb.substring(7, 12)); // World
length() and capacity()
StringBuffer sb = new StringBuffer("Hello");
System.out.println(sb.length()); // 5 — number of chars currently stored
System.out.println(sb.capacity()); // 21 — internal array size (5 + 16 default)
charAt() and setCharAt()
StringBuffer sb = new StringBuffer("Hello");
System.out.println(sb.charAt(1)); // e
sb.setCharAt(0, 'h');
System.out.println(sb); // hello
Thread Safety
Every mutating method in StringBuffer — append, insert, delete, replace, reverse — is declared synchronized. This means only one thread can call a method on a given StringBuffer instance at a time, making it safe for concurrent use.
StringBuffer log = new StringBuffer();
Runnable task = () -> {
for (int i = 0; i < 100; i++) {
log.append("x"); // safe from multiple threads
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(log.length()); // always 200
Warning:
synchronizedprevents data corruption, but it does not make a compound operation like read-then-write atomic. If you need higher-level atomicity, usejava.util.concurrentutilities.
Under the Hood
Internally, StringBuffer stores characters in a char[] (prior to Java 9) or a byte[] with a coder field (Java 9+ compact strings). The buffer starts at 16 characters of headroom beyond your initial content.
Capacity growth: When an append would exceed the current capacity, StringBuffer allocates a new array of size (oldCapacity × 2) + 2 and copies the existing data over. This amortises the cost of resizing, giving you O(1) amortised append performance — similar to ArrayList’s growth strategy.
The synchronized keyword compiles to MONITORENTER / MONITOREXIT bytecodes. Acquiring an uncontended monitor in the JVM is cheap (a single CAS on the object header), but under heavy contention threads block and context-switch — which is why StringBuilder exists for single-threaded use.
StringBuffer vs StringBuilder
| Feature | StringBuffer | StringBuilder |
|---|---|---|
| Thread-safe | Yes (synchronized) | No |
| Performance | Slightly slower | Faster (no lock overhead) |
| Introduced in | Java 1.0 | Java 5 |
| Use case | Shared mutable strings across threads | Building strings in a single thread |
Tip: In most single-threaded code — including the overwhelming majority of everyday string building — prefer StringBuilder. Only reach for
StringBufferwhen multiple threads genuinely share the same buffer object.
Quick Reference: Full 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 |
Practical Example: Building a CSV Row
public class CsvBuilder {
public static String buildRow(String... values) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < values.length; i++) {
sb.append(values[i]);
if (i < values.length - 1) {
sb.append(',');
}
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(buildRow("Alice", "30", "Engineer"));
}
}
Output:
Alice,30,Engineer
This avoids allocating a temporary String on every loop iteration — a real gain when you’re building hundreds of rows.
Related Topics
- StringBuilder — the faster, non-synchronized sibling; prefer it for single-threaded code
- StringBuffer vs StringBuilder — side-by-side comparison to help you pick the right class
- String vs StringBuffer — when immutable
Stringis the better choice - String Immutability — why
Stringis immutable and what that means for performance - String Methods — the full API of the immutable
Stringclass - Synchronization — the locking mechanism that makes
StringBufferthread-safe