Java StringBuffer
Use the thread-safe StringBuffer class in Java for mutable strings shared across threads.
Java StringBuffer
StringBuffer is the older sibling of StringBuilder. They share an API, they share a parent (AbstractStringBuilder), and they share a buffer-of-bytes implementation. The single difference is that StringBuffer's methods are synchronized — every append, insert, delete, and toString acquires a monitor on the buffer for the duration of the call. That makes a StringBuffer safe to share across threads. It also makes every operation slower than the equivalent on StringBuilder.
The original Java class library shipped only StringBuffer. StringBuilder arrived in JDK 1.5 (2004) precisely because the synchronisation overhead was a problem in the common single-threaded case. For the past two decades, StringBuilder has been the default and StringBuffer the niche choice.
When to actually use it
The honest summary first: almost never. The list of real use cases is short, and most of them have better answers in modern Java.
A StringBuffer is the right call only when every one of these is true:
- One buffer is genuinely shared across multiple threads.
- Each thread independently appends or inserts into it.
- A final, single immutable
Stringis what the consumer needs at the end. - You can't easily restructure the code so each thread builds its own
StringBuilderand a coordinator joins them.
In most concurrent code, the last bullet is the escape hatch: give every thread its own StringBuilder, return the strings, concatenate at the end. That avoids contention on a single monitor and removes the synchronisation overhead from the hot path.
The remaining case — a small number of threads tracing into a shared diagnostic buffer, an audit log appended by several actors — is where StringBuffer still earns its keep.
The API mirrors StringBuilder
Every mutator on StringBuilder exists on StringBuffer with the same signature and the same return type. Because both classes extend AbstractStringBuilder, they're behavioural twins; the only difference is locking:
StringBuffer sb = new StringBuffer(64);
sb.append("Hello, ")
.append("world")
.insert(0, "[INFO] ")
.append('!');
String out = sb.toString();Constructors, length(), capacity(), charAt, substring, indexOf, reverse, delete, replace, setLength, ensureCapacity, trimToSize — all present, all return the same types. Code review for StringBuffer is essentially the same review as for StringBuilder, plus a note about which monitor is held.
What synchronisation means here
synchronized on instance methods locks on the buffer object itself. So:
StringBuffer log = new StringBuffer();
// Thread A
log.append("hit /users\n");
// Thread B (concurrently)
log.append("hit /orders\n");Each append runs atomically with respect to the other — neither thread will tear the other's bytes. The result will be "hit /users\nhit /orders\n" or "hit /orders\nhit /users\n". It will not be "hit /uhit /ordserss\n\n". That's the guarantee.
The guarantee does not extend across method calls. A sequence of two appends is not atomic:
log.append(level); // unlock
// ← another thread might append here
log.append(": ");
log.append(message);
log.append('\n');A second thread can slot a write in between the first two appends and interleave. If you want a whole record to land contiguously, do the assembly in a thread-local StringBuilder first and then append the finished string once to the shared StringBuffer. Or — equivalently — wrap the multi-step write in an explicit synchronized (log) { ... } block.
Performance, briefly
Every locked call pays for the monitor: in modern HotSpot that's biased-lock-fast-path cheap when uncontended, and noticeably more under contention. Compared to StringBuilder, a single uncontended append is at most a small constant factor slower. Under contention it's dramatically slower, because threads block waiting for the monitor.
The takeaway: contention is the cost, not the locking primitive itself. If you've measured a hot, contended StringBuffer, the right fix is restructuring so each thread builds its own piece — not further tuning of the buffer.
A worked example
The program below starts a small pool of writer threads that share one StringBuffer and append marker lines. The synchronised methods keep each line intact; the deliberate two-step write demonstrates why you sometimes still need an outer synchronized block to keep a group of writes together.
Look at the run output and you'll see two patterns. The [T?:i] markers are individually intact — no torn tags — because each one is a single append call. But the order in which different threads' markers appear is interleaved. The three -- end of T? -- lines, by contrast, each land contiguously, because the outer synchronized (shared) { ... } block holds the lock across all four appends for that group.
What's next
That covers mutable string assembly in both flavours. Producing strings for display is the next concern: rendering numbers with a fixed width, formatting dates, padding columns. Continue to Java String formatting.
Practice
Which statement most accurately describes the difference between `StringBuffer` and `StringBuilder`?