Back to String & String Pool
Q5Question 5 of 12

What is the difference between String, StringBuilder, and StringBuffer?

Key Concepts
  • String: immutable. Every modification creates a new object. Thread-safe by nature.
  • StringBuilder: mutable, NOT thread-safe. Fast for single-threaded string building.
  • StringBuffer: mutable, thread-safe (synchronized methods). Slower than StringBuilder.
  • Use StringBuilder for loops/concatenation in single-threaded code
  • Use StringBuffer only when multiple threads share the same builder (rare)
  • Since Java 9+, the compiler optimizes String + with invokedynamic, reducing the need for manual StringBuilder

Definition

String is immutable (new object per change). StringBuilder is mutable and fast. StringBuffer is mutable and thread-safe (synchronized). Use StringBuilder by default.

Three string types:

  • String: Immutable. s += "x" creates a new String. Safe to share but slow for repeated modifications.
  • StringBuilder: Mutable. Modifies in place without creating new objects. NOT thread-safe. Use for single-threaded building.
  • StringBuffer: Mutable. Same API as StringBuilder but all methods are synchronized. Thread-safe but slower.

String is a printed bank statement – any change produces a new printout. StringBuilder is a whiteboard – erase and write freely, fast. StringBuffer is a whiteboard with a lock – only one person writes at a time, slower but safe.

Understanding the Concepts

Comparison table

The three types serve different purposes and have distinct performance characteristics.

| Feature | String | StringBuilder | StringBuffer |
|---|---|---|---|
| Mutable? | No | Yes | Yes |
| Thread-safe? | Yes (immutable) | No | Yes (synchronized) |
| Performance | Slow for loops | Fast | Slower than StringBuilder |
| Since | Java 1.0 | Java 1.5 | Java 1.0 |
| Use when | Simple values, constants | Loop building, single thread | Multi-thread building (rare) |

The loop problem

String concatenation with += in a loop creates a new String object on every iteration. For N iterations, this creates approximately N intermediate objects, generating garbage and causing GC pressure.

JAVA
package com.bank.report;

import java.math.BigDecimal;

public class StatementBuilder {
    // BAD: creates ~10,000 String objects
    public String buildBad() {
        String result = "";
        for (int i = 0; i < 10_000; i++) {
            result += "TXN" + i + ": Rs.1000\n";  // new String each iteration
        }
        return result;
    }

    // GOOD: one StringBuilder, modifies in place
    public String buildGood() {
        StringBuilder sb = new StringBuilder(256);  // initial capacity
        for (int i = 0; i < 10_000; i++) {
            sb.append("TXN").append(i).append(": Rs.1000\n");
        }
        return sb.toString();
    }
}

For a banking statement with 10,000 transactions, the StringBuilder version is roughly 100x faster because it modifies a single internal buffer instead of creating thousands of intermediate String objects.

StringBuilder API

StringBuilder provides a rich API for building strings in place.

JAVA
StringBuilder sb = new StringBuilder("Account: ");
sb.append("SAV2001");                   // append text
sb.append(" Balance: ").append(500000); // chaining
sb.insert(0, "[BANK] ");               // insert at position
sb.delete(0, 7);                        // delete range [0, 7)
sb.replace(0, 7, "ACCT");              // replace range
System.out.println(sb.length());        // current length
System.out.println(sb.capacity());      // internal buffer size
String result = sb.toString();          // convert to immutable String

When do you actually need StringBuffer?

Almost never. In modern Java:

  • Single-threaded: use StringBuilder.
  • Multi-threaded: each thread builds its own StringBuilder (no sharing needed).
  • StringBuffer is legacy. Use it only when maintaining old code that explicitly shares a buffer across threads.
JAVA
package com.bank.service;

import java.util.List;
import java.util.stream.Collectors;

public class TransactionFormatter {
    // Modern approach: no shared mutable state needed
    public String formatTransactions(List<String> txnIds) {
        return txnIds.stream()
                     .map(id -> "TXN: " + id)
                     .collect(Collectors.joining("\n"));
    }
}

What about Java 9+ String concatenation optimization?

Since Java 9, the compiler uses invokedynamic and StringConcatFactory for + concatenation, which is often faster than manual StringBuilder for simple expressions.

JAVA
// Java 9+: compiled with invokedynamic, efficient
String info = "Account: " + accountId + " Balance: " + balance;
// This is fine – no need for StringBuilder here

// But in LOOPS, StringBuilder is still necessary:
String result = "";
for (int i = 0; i < 1000; i++) {
    result += i;  // Still bad! The compiler does NOT optimize += in loops
}

The key rule: simple concatenation outside loops is fine with +. Inside loops, always use StringBuilder.

What about String.join() and Collectors.joining()?

For joining collections, Java provides cleaner alternatives to manual StringBuilder.

JAVA
// String.join() – joining with a delimiter
List<String> accounts = List.of("SAV2001", "CUR3001", "LOAN5001");
String joined = String.join(", ", accounts);  // "SAV2001, CUR3001, LOAN5001"

// Collectors.joining() – in a stream pipeline
String csv = accounts.stream()
    .collect(Collectors.joining(",", "[", "]"));  // "[SAV2001,CUR3001,LOAN5001]"

What should a senior engineer know?

  • StringBuilder initial capacity: new StringBuilder(256) avoids resizing. Default is 16 characters. When the buffer fills, it doubles in size and copies, so pre-sizing for the expected output length saves allocations.
  • StringBuilder is not thread-safe. If you need a shared builder (rare), use StringBuffer or synchronize externally.
  • In banking at National Bank of India, Rajesh uses StringBuilder for generating bank statements and transaction reports in batch jobs. For simple log messages, + concatenation is fine since Java 9.

Visual Overview

DIAGRAM
  String:        immutable, new object per change
  StringBuilder: mutable, not synchronized, fast
  StringBuffer:  mutable, synchronized, thread-safe, slow

  Loop:
    String +=    → N new objects → slow, GC pressure
    StringBuilder.append → 1 object, in-place → fast

  Rule: StringBuilder by default
        StringBuffer only for legacy multi-thread code
        String for simple values and constants

Key Takeaway

String is immutable – every modification creates a new object. StringBuilder is mutable and fast for single-threaded building. StringBuffer is mutable and thread-safe but slower. Use StringBuilder for loops and concatenation. Use StringBuffer only in legacy code that shares a builder across threads.

Since Java 9, simple + concatenation is optimized by the compiler. But in loops, StringBuilder is still essential.

One sentence to remember: String is immutable, StringBuilder is fast and mutable, StringBuffer adds synchronization – use StringBuilder by default.

---

Share
Use ← → arrow keys to navigate