StringBuilder and StringBuffer in Java

3 minute read

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 StringBuilder by default. Use StringBuffer only 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

  1. Simple concatenation — the compiler optimises "a" + "b" + "c" into a single string at compile time.
  2. String.format() — more readable for formatted output.
  3. 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

  1. Never use + concatenation in loops — use StringBuilder.
  2. StringBuilder is mutable and fast (not thread-safe).
  3. StringBuffer is thread-safe but slower — rarely needed.
  4. Pre-size the builder if you know the approximate length.
  5. For simple concatenation outside loops, + and String.join() are fine.

What’s Next?

Next: String Formatting — String.format(), printf(), format specifiers, and text blocks.

Updated: