StringBuffer Class in Java – Mutable, Thread-Safe Strings
StringBuffer is a mutable sequence of characters in java.lang. Unlike String, its contents can be changed in place with methods such as append, insert and delete, without creating a new object each time. All of its public methods are synchronized, so a single StringBuffer can be safely shared between threads.
Since Java 5, the unsynchronized StringBuilder is preferred in most code. This tutorial covers StringBuffer's constructors, methods and capacity management, and explains when its thread safety actually matters.
1. Constructors
| Constructor | Initial capacity |
|---|---|
StringBuffer() | 16 characters |
StringBuffer(int capacity) | The given capacity |
StringBuffer(String str) | 16 + str.length(), initialised with str |
StringBuffer(CharSequence seq) | 16 + seq.length() |
2. Common Methods
public class StringBufferMethods {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Java");
sb.append(" Tutorial"); System.out.println("append : " + sb);
sb.insert(5, "Core "); System.out.println("insert : " + sb);
sb.replace(5, 9, "Advanced"); System.out.println("replace : " + sb);
sb.delete(4, 13); System.out.println("delete : " + sb);
sb.deleteCharAt(sb.length() - 1); System.out.println("deleteCharAt: " + sb);
sb.setCharAt(0, 'j'); System.out.println("setCharAt : " + sb);
sb.reverse(); System.out.println("reverse : " + sb);
sb.reverse();
System.out.println("indexOf(\"Tut\"): " + sb.indexOf("Tut"));
System.out.println("charAt(1) : " + sb.charAt(1));
System.out.println("length : " + sb.length());
// Methods return the same object, so calls can be chained
System.out.println("chained : " + new StringBuffer().append("A").append(1).append(true).append(2.5));
}
}
append : Java Tutorial
insert : Java Core Tutorial
replace : Java Advanced Tutorial
delete : Java Tutorial
deleteCharAt: Java Tutoria
setCharAt : java Tutoria
reverse : airotuT avaj
indexOf("Tut"): 5
charAt(1) : a
length : 12
chained : A1true2.5
| Method | Purpose |
|---|---|
append(x) | Add any type (String, int, char, boolean, Object…) at the end |
insert(index, x) | Insert at a position |
replace(start, end, str) | Replace characters from start (inclusive) to end (exclusive) |
delete(start, end) / deleteCharAt(i) | Remove a range or one character |
reverse() | Reverse the contents in place |
setCharAt(i, c) / charAt(i) | Write or read a character |
indexOf / lastIndexOf | Search for a substring |
substring | Copy part as a new String |
toString() | Produce an immutable String |
Most modifying methods return this, which allows method chaining.
3. Length vs Capacity
Length is the number of characters stored; capacity is the size of the internal array. When the content outgrows the capacity, the buffer allocates a larger array (old capacity × 2 + 2) and copies the characters:
public class CapacityDemo {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer(); // default capacity 16
System.out.println("empty: length=" + sb.length() + ", capacity=" + sb.capacity());
sb.append("1234567890123456"); // exactly 16 chars
System.out.println("16 chars: length=" + sb.length() + ", capacity=" + sb.capacity());
sb.append("x"); // grows: (16 * 2) + 2 = 34
System.out.println("17 chars: length=" + sb.length() + ", capacity=" + sb.capacity());
StringBuffer sized = new StringBuffer(100); // pre-size when the final length is known
System.out.println("pre-sized: capacity=" + sized.capacity());
StringBuffer fromText = new StringBuffer("Java"); // capacity = 16 + 4
System.out.println("from \"Java\": capacity=" + fromText.capacity());
sb.setLength(5); // truncate
sb.trimToSize(); // shrink capacity to length
System.out.println("after setLength(5) + trimToSize(): " + sb + ", capacity=" + sb.capacity());
}
}
empty: length=0, capacity=16 16 chars: length=16, capacity=16 17 chars: length=17, capacity=34 pre-sized: capacity=100 from "Java": capacity=20 after setLength(5) + trimToSize(): 12345, capacity=5
If you know roughly how long the result will be, pass that capacity to the constructor or call ensureCapacity() to avoid repeated resizing.
4. Thread Safety
Three threads append 10,000 characters each to a shared StringBuffer and a shared StringBuilder:
public class ThreadSafeBuffer {
public static void main(String[] args) throws InterruptedException {
StringBuffer buffer = new StringBuffer(); // synchronized methods
StringBuilder builder = new StringBuilder(); // not synchronized
Runnable task = () -> {
for (int i = 0; i < 10_000; i++) {
buffer.append('a');
try { builder.append('a'); } catch (ArrayIndexOutOfBoundsException e) { /* corrupted state */ }
}
};
Thread t1 = new Thread(task), t2 = new Thread(task), t3 = new Thread(task);
t1.start(); t2.start(); t3.start();
t1.join(); t2.join(); t3.join();
System.out.println("Expected length: 30000");
System.out.println("StringBuffer length: " + buffer.length());
System.out.println("StringBuilder length: " + builder.length() + " (often less: lost updates)");
}
}
Expected length: 30000 StringBuffer length: 30000 StringBuilder length: 29763 (often less: lost updates)
Each StringBuffer method call is atomic. Note, however, that a sequence of calls, such as checking length() and then appending, is not atomic; you would still need external synchronization for that. In practice, sharing a mutable string builder between threads is rare, which is why StringBuilder is the usual choice.
5. String vs StringBuffer vs StringBuilder
| Aspect | String | StringBuffer | StringBuilder |
|---|---|---|---|
| Mutability | Immutable | Mutable | Mutable |
| Thread safety | Yes (immutable) | Yes (synchronized) | No |
| Performance for many modifications | Slow (new objects) | Fast | Fastest |
| Since | Java 1.0 | Java 1.0 | Java 5 |
equals() compares content | Yes | No (identity); use compareTo or toString() | No (identity) |
6. Summary
StringBufferis a mutable, synchronized character sequence.- Use
append,insert,replace,deleteandreverseto modify it in place, thentoString(). - Default capacity is 16 and grows to (old × 2 + 2) when needed.
- Prefer
StringBuilderunless the same instance is shared between threads.
Related Tutorials
Frequently Asked Questions (FAQ)
Q1. What is StringBuffer in Java?
A mutable, thread-safe sequence of characters whose contents can be modified without creating new objects.
Q2. What is the default capacity of StringBuffer?
16 characters. With a String argument, it is 16 plus the string's length.
Q3. How does StringBuffer capacity grow?
When full, it becomes (old capacity × 2) + 2, or the required length if that is larger.
Q4. Is StringBuffer thread-safe?
Yes. Its public methods are synchronized, so individual calls are atomic.
Q5. What is the difference between StringBuffer and StringBuilder?
They have the same API, but StringBuffer is synchronized and StringBuilder is not, which makes StringBuilder faster.
Q6. What is the difference between String and StringBuffer?
String is immutable; every change creates a new object. StringBuffer is modified in place.
Q7. Does StringBuffer override equals()?
No. It compares references. Use sb1.compareTo(sb2) == 0 (Java 11) or compare toString() results.
Q8. How do I convert a StringBuffer to a String?
Call toString().
Q9. How do I reverse a string with StringBuffer?
new StringBuffer(s).reverse().toString().
Q10. What is the difference between length() and capacity()?
length() is the number of characters stored; capacity() is how many fit before the internal array must grow.
Q11. What does setLength() do?
It truncates the contents or pads them with null characters ('\u0000') to the given length.
Q12. What does trimToSize() do?
It may shrink the internal array to match the current length, saving memory.
Q13. Is StringBuffer final?
Yes. Like String and StringBuilder, it cannot be subclassed.
Q14. Can StringBuffer be used as a HashMap key?
It is a poor choice: it is mutable and uses identity-based equals and hashCode. Use its String value instead.
Q15. What happens if an index is out of range?
Methods like charAt, insert and delete throw StringIndexOutOfBoundsException.
Q16. Is StringBuffer deprecated?
No, but it is considered legacy for most uses. New code typically uses StringBuilder.
Q17. Which interfaces does StringBuffer implement?
CharSequence, Appendable, Serializable and, since Java 11, Comparable<StringBuffer>.
Q18. Is a sequence of StringBuffer calls atomic?
No. Each call is synchronized individually; compound operations need an external synchronized block.
Q19. Can append() accept any type?
Yes. There are overloads for all primitives, char[], String, CharSequence and Object (via toString()).
Q20. When should I still use StringBuffer?
When one instance really is shared between threads, or when an older API requires it (for example some regex Matcher methods before Java 9).