StringBuilder and StringBuffer in Java
Strings in Java are immutable. Every time you concatenate with +, a new String object is created. In a loop, this becomes a serious performance problem. That’s where StringBuilder and StringBuffer come in.
The Problem with + in Loops
// BAD — O(n²) time, creates ~10,000 String objects
String result = "";
for (int i = 0; i < 10_000; i++) {
result += i + " ";
}
Each += creates a new String, copies all previous content, then appends. The total work grows quadratically.
StringBuilder — The Fix
StringBuilder is a mutable sequence of characters. Appending is O(1) amortised.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10_000; i++) {
sb.append(i).append(" ");
}
String result = sb.toString();
Core Methods
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // "Hello World"
sb.insert(5, ","); // "Hello, World"
sb.delete(5, 6); // "Hello World"
sb.replace(6, 11, "Java"); // "Hello Java"
sb.reverse(); // "avaJ olleH"
sb.length(); // 10
sb.charAt(0); // 'a'
sb.setCharAt(0, 'A'); // "AvaJ olleH"
sb.deleteCharAt(0); // "vaJ olleH"
String s = sb.toString(); // convert to String
Method Chaining
All mutator methods return this, enabling chaining:
String csv = new StringBuilder()
.append("Alice")
.append(",")
.append("Bob")
.append(",")
.append("Charlie")
.toString();
// "Alice,Bob,Charlie"
Capacity Management
StringBuilder sb = new StringBuilder(); // default capacity: 16
StringBuilder sb2 = new StringBuilder(100); // initial capacity: 100
sb.capacity() // 16
sb.append("Hello, World! This is a longer string.");
sb.capacity() // auto-expanded (typically doubles + 2)
sb.trimToSize(); // reduce capacity to current length
Pre-sizing avoids unnecessary resizing:
// If you know the approximate final size
StringBuilder sb = new StringBuilder(10_000);
StringBuilder vs StringBuffer
| Feature | StringBuilder | StringBuffer |
|---|---|---|
| Mutable | ✅ | ✅ |
| Thread-safe | ❌ | ✅ (synchronized) |
| Performance | ⚡ Faster | 🐢 Slower |
| Introduced | Java 5 | Java 1.0 |
| Use when | Single-threaded | Multi-threaded |
StringBuffer has the exact same API as StringBuilder — every method is synchronized:
StringBuffer sbuf = new StringBuffer("Thread-safe");
sbuf.append(" string");
Rule of thumb: Use
StringBuilderby default. UseStringBufferonly if multiple threads will access the same builder — which is extremely rare in practice.
Performance Benchmark
Building a 100,000-character string:
| Approach | Time | Objects Created |
|---|---|---|
String with + |
~4,500 ms | ~100,000 |
StringBuffer |
~5 ms | 1 |
StringBuilder |
~3 ms | 1 |
Results are approximate and JVM-dependent, but the order of magnitude is consistent.
Real-World Examples
Building SQL Queries
public String buildQuery(String table, List<String> columns, String where) {
StringBuilder sql = new StringBuilder("SELECT ");
sql.append(String.join(", ", columns));
sql.append(" FROM ").append(table);
if (where != null && !where.isBlank()) {
sql.append(" WHERE ").append(where);
}
return sql.toString();
}
Building CSV Output
public String toCsv(List<String[]> rows) {
StringBuilder csv = new StringBuilder();
for (String[] row : rows) {
csv.append(String.join(",", row)).append("\n");
}
return csv.toString();
}
Reversing a String
String reversed = new StringBuilder("Hello").reverse().toString();
// "olleH"
When NOT to Use StringBuilder
- Simple concatenation — the compiler optimises
"a" + "b" + "c"into a single string at compile time. - String.format() — more readable for formatted output.
- String.join() — cleaner for joining with a delimiter.
// These are fine without StringBuilder
String name = firstName + " " + lastName; // compiler optimises
String csv = String.join(",", "a", "b", "c"); // cleaner
String msg = String.format("Hello, %s!", name); // more readable
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
Using StringBuffer everywhere |
Unnecessary synchronisation overhead | Use StringBuilder |
Not calling toString() |
StringBuilder is not a String |
Always convert at the end |
Creating StringBuilder inside a loop |
Defeats the purpose | Create once, before the loop |
Using + inside sb.append() |
Creates intermediate String | Chain .append() calls |
// BAD
sb.append("Name: " + name + ", Age: " + age);
// GOOD
sb.append("Name: ").append(name).append(", Age: ").append(age);
Key Takeaways
- Never use
+concatenation in loops — useStringBuilder. StringBuilderis mutable and fast (not thread-safe).StringBufferis thread-safe but slower — rarely needed.- Pre-size the builder if you know the approximate length.
- For simple concatenation outside loops,
+andString.join()are fine.
What’s Next?
Next: String Formatting — String.format(), printf(), format specifiers, and text blocks.