By WebNest Studio Engineering Team
Java - Core Tutorial
Java StringBuffer
StringBuffer is a mutable sequence of characters, created specifically to solve a performance problem: repeatedly concatenating immutable Strings (with +) creates a new object every time, which wastes memory and CPU cycles in a loop. StringBuffer instead maintains an internal, resizable character array that can be modified directly, without creating a new object for every change.
StringBuffer has existed in Java since version 1.0, and its defining characteristic is that all of its public methods are synchronized. This makes a single StringBuffer instance safe to share and mutate from multiple threads at once, at the cost of the extra overhead that synchronization always introduces.
Why it matters
When text is built up in many steps, creating a new String at every step wastes time and memory. StringBuffer is the original mutable alternative and is still found throughout older code and libraries. You need to recognise it, know that its methods are synchronized, and understand why modern code usually reaches for StringBuilder instead.
Watch: How StringBuffer Beats Immutable Strings
A quick video on why modifying a StringBuffer in place avoids the new object that every String change creates.
How StringBuffer Beats Immutable Strings
A String can never be changed, so every "modification" — concat(), +, replace() — has to allocate a new String object and copy the old characters into it. The original object is left behind for the garbage collector. Do that a thousand times in a loop and you have created a thousand objects to end up with one piece of text.
A StringBuffer works the other way round. It owns one internal character array, and append(), insert() and reverse() write into that same array. No new object is created per change; the buffer only allocates a larger array when it runs out of room. You can see the difference directly: append() returns the very same StringBuffer it was called on, while concat() returns a different String.
Core Mutating Methods
StringBuffer's API is built around modifying its internal buffer directly rather than returning new objects.
append(...)— adds text to the end of the buffer; overloaded for String, char, int, boolean, and more.insert(int offset, ...)— inserts text at a specific position.reverse()— reverses the character sequence in place.delete(int start, int end)anddeleteCharAt(int index)— remove characters from the buffer.replace(int start, int end, String str)— replaces a range of characters with new text.
Thread Safety and Its Cost
Every mutating method on StringBuffer is marked synchronized, meaning only one thread can execute a method on a given StringBuffer instance at a time. This guarantees correct behavior when several threads append to the same buffer concurrently, but the locking overhead makes StringBuffer measurably slower than its non-synchronized counterpart, StringBuilder, in single-threaded code — which is the overwhelming majority of real-world use cases.
Converting Back to String
Once you finish building or modifying text with a StringBuffer, call toString() to obtain an immutable String snapshot of the current content. The StringBuffer itself can continue to be reused and modified afterward, independently of any String already produced from it.
Examples
Building and modifying text with StringBuffer
public class StringBufferDemo {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Java");
sb.append(" is").append(" powerful");
System.out.println(sb);
sb.insert(4, " language");
System.out.println(sb);
sb.replace(0, 4, "Kotlin");
System.out.println(sb);
sb.reverse();
System.out.println(sb);
}
}
Java is powerful
Java language is powerful
Kotlin language is powerful
lufrewop si egaugnal niltoK
A String change creates a new object; a StringBuffer change reuses the same one
public class MutableVsImmutable {
public static void main(String[] args) {
String text = "Java";
String changed = text.concat(" 17");
System.out.println(text == changed); // different objects
System.out.println(text); // the original is untouched
StringBuffer buffer = new StringBuffer("Java");
StringBuffer same = buffer.append(" 17");
System.out.println(buffer == same); // the same object
System.out.println(buffer); // it was modified in place
}
}
false
Java
true
Java 17
Common Mistakes
- Using StringBuffer by default for single-threaded string building, paying for synchronization overhead that provides no benefit there.
- Forgetting that append() and insert() return "this" (the same StringBuffer), which allows chaining, but mistakenly assigning the result to a String variable directly instead of calling toString().
- Assuming StringBuffer is immutable like String, and being surprised that methods change the object in place rather than returning a new one.
Exercise
Build the text 1-2-3-4-5 with a StringBuffer and a loop, making sure there is no dash at the end.
Expected output: 1-2-3-4-5
public class DashList {
public static void main(String[] args) {
StringBuffer buffer = new StringBuffer();
// TODO: append the numbers 1 to 5 separated by dashes, with no trailing dash
System.out.println(buffer);
}
}
Show hints
- Append the number and then a dash on each pass, and remove the last character after the loop.
buffer.deleteCharAt(buffer.length() - 1)removes the final character.
Show solution
public class DashList {
public static void main(String[] args) {
StringBuffer buffer = new StringBuffer();
for (int i = 1; i <= 5; i++) {
buffer.append(i).append('-');
}
buffer.deleteCharAt(buffer.length() - 1);
System.out.println(buffer); // 1-2-3-4-5
}
}
Quick quiz
1. What does new StringBuffer("abc").reverse().toString() return?
- A. abc
- B. cba
- C. bca
- D. It does not compile
Show answer
B. reverse() reverses the characters in place and returns the same buffer, so the result is "cba".
2. What makes StringBuffer thread-safe?
- A. It is immutable
- B. It copies itself for each thread
- C. Its methods are synchronized
- D. It is stored in the string pool
Show answer
C. Its public methods are synchronized, so only one thread can modify a given buffer at a time.
3. A StringBuffer contains "yz". What does it contain after insert(0, "x")?
- A. yzx
- B. x
- C. yxz
- D. xyz
Show answer
D. insert(0, ...) places the text at the start and shifts the existing characters to the right.
Interview questions
What is the default capacity of a StringBuffer and how does it grow?
Show answer
An empty StringBuffer starts with room for 16 characters. When more space is needed, the internal array is replaced with a larger one of roughly double the old capacity plus two, and the existing characters are copied across. If you know the final size in advance, passing it to the constructor avoids those copies.
Why is StringBuffer slower than StringBuilder?
Show answer
Every StringBuffer method is synchronized, so each call has to acquire and release a lock even when only one thread is using the buffer. StringBuilder has the same methods without synchronization, so it avoids that overhead. When the buffer is a local variable used by one thread, the locking gives no benefit.
Key Points to Remember
- StringBuffer is mutable — its methods modify the internal character buffer directly.
- All public methods are synchronized, making it thread-safe but slower than StringBuilder.
- Use toString() to get an immutable String snapshot of the buffer's current content.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering