Skip to content
Java strings 5 min read

StringBuffer

When you need to build or modify a string repeatedly — especially in a multi-threaded environmentStringBuffer 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). StringBuffer is 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 StringBufferappend, 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: synchronized prevents data corruption, but it does not make a compound operation like read-then-write atomic. If you need higher-level atomicity, use java.util.concurrent utilities.

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

FeatureStringBufferStringBuilder
Thread-safeYes (synchronized)No
PerformanceSlightly slowerFaster (no lock overhead)
Introduced inJava 1.0Java 5
Use caseShared mutable strings across threadsBuilding strings in a single thread

Tip: In most single-threaded code — including the overwhelming majority of everyday string building — prefer StringBuilder. Only reach for StringBuffer when multiple threads genuinely share the same buffer object.

Quick Reference: Full Method Table

MethodDescription
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.

Last updated June 13, 2026
Was this helpful?