</>SyntaxPilot
Notes › Java › Episode 10

Java StringBuilder and StringBuffer: Mutable Strings, Methods and Performance

▶ Watch the videoTry the code in the compilerAll Java notes

These are the notes for Java episode 10. You will learn why s = s + i in a loop is slow (we time it live with System.currentTimeMillis()), what a StringBuilder is (text you can change in place), its methods append, insert, replace, delete, deleteCharAt, setCharAt and reverse, method chaining, a one-line palindrome check, three StringBuilder traps, capacity and how a builder grows, and StringBuffer, the synchronized twin for many threads. Every example below runs as-is in the SyntaxPilot compiler, with its real output from javac 21 (timings vary from machine to machine and run to run).

The problem: every ticket number in one line

The college fest sold 50,000 tickets, numbered from 0. The gate scanner wants them all in one line: 0,1,2,3,.... The obvious way, from episode 9, is s = s + i + "," in a loop. This time we also time it.

System.currentTimeMillis() returns the current time in milliseconds (thousandths of a second) as a long. Take it before and after, and subtract.

public class Main {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        String s = "";
        for (int i = 0; i < 50000; i++) {
            s = s + i + ",";
        }
        long end = System.currentTimeMillis();
        System.out.println("String: " + (end - start) + " ms, " + s.length() + " chars");
    }
}
String: 1405 ms, 288890 chars

Well over a second for one String. With 100,000 tickets it took about 5,200 ms on the same machine: double the work, roughly four times the time.

Why: Strings are immutable. Every + builds a new String and copies every old character into it. Round 1 copies 2 characters, round 2 copies 4, ..., round 50,000 copies about 289,000. Added up, the loop copies billions of characters just to throw them away. The cost grows with the square of the number of rounds.

The fix: StringBuilder

Real-life example: a whiteboard.

In a classroom In Java
a printed certificate: any change means a reprint String: immutable
a whiteboard: write, erase, add more on the same board StringBuilder: mutable, changed in place
take a photo when you're done sb.toString(): a fixed String to keep

Mutable means "can be changed after it's created".

public class Main {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 50000; i++) {
            sb.append(i).append(",");
        }
        String s = sb.toString();
        long end = System.currentTimeMillis();
        System.out.println("StringBuilder: " + (end - start) + " ms, " + s.length() + " chars");
    }
}
StringBuilder: 11 ms, 288890 chars

The same 288,890 characters, more than a hundred times faster. append writes at the end of the existing text without copying it.

Method chaining: append returns the same builder, so you can call the next method straight on it: sb.append(i).append(",").

Note: StringBuilder lives in java.lang, like String, so it needs no import.

StringBuilder methods

Method What it does
append(x) adds x (any type) at the end
insert(index, x) inserts x at index
replace(start, end, str) replaces characters start to end - 1 with str
delete(start, end) removes characters start to end - 1
deleteCharAt(i) removes the character at i
setCharAt(i, c) changes the character at i to c
reverse() reverses the text
charAt(i), indexOf(str), length() read it, like a String
toString() returns the text as a String

Like substring, ranges are start included, end excluded. Every editing method changes the builder itself and returns it (so they chain).

public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("chai");
        sb.append(" 20");
        System.out.println(sb);
        sb.insert(0, "Masala ");
        System.out.println(sb);
        sb.replace(12, 14, "25");
        System.out.println(sb);
        sb.delete(0, 7);
        System.out.println(sb);
        sb.setCharAt(0, 'C');
        System.out.println(sb + " | length " + sb.length());
    }
}
chai 20
Masala chai 20
Masala chai 25
chai 25
Chai 25 | length 7
 M a s a l a _ c h a i _ 2 0
 0 1 2 3 4 5 6 7 8 9 ...  12 13      replace(12, 14, "25") swaps index 12 and 13

println(sb) works directly because println calls toString() for us; so does sb + " | ...".

Palindrome check in one line

public class Main {
    public static void main(String[] args) {
        String[] words = {"madam", "level", "java"};
        for (String w : words) {
            String back = new StringBuilder(w).reverse().toString();
            System.out.println(w + " -> " + back + " : " + w.equals(back));
        }
    }
}
madam -> madam : true
level -> level : true
java -> avaj : false

new StringBuilder(w) makes a builder holding a copy of w; reversing it never changes w.

Three StringBuilder traps

1. A StringBuilder is not a String

StringBuilder sb = new StringBuilder("Priya");
String name = sb;
Main.java:4: error: incompatible types: StringBuilder cannot be converted to String
        String name = sb;
                      ^
1 error

Fix: String name = sb.toString();

2. equals on two builders compares objects, not text

public class Main {
    public static void main(String[] args) {
        StringBuilder a = new StringBuilder("chai");
        StringBuilder b = new StringBuilder("chai");
        System.out.println(a.equals(b));
        System.out.println(a.toString().equals(b.toString()));
        System.out.println(a.compareTo(b) == 0);
    }
}
false
true
true

String overrides equals to compare characters; StringBuilder doesn't, so its equals only asks "same object?". Compare toString()s, or use compareTo (Java 11+), which returns 0 when the text is the same.

3. Two names, one builder

public class Main {
    public static void main(String[] args) {
        StringBuilder order = new StringBuilder("tea");
        StringBuilder copy = order;
        copy.append(" + bun");
        System.out.println(order);
    }
}
tea + bun

copy = order copies the reference, as with arrays in episode 7. Both names point to one builder. With Strings this never hurts, because nobody can change a String; with a mutable object, it does. A real copy: new StringBuilder(order).

Capacity: how a StringBuilder grows

Real-life example: a notebook with spare pages.

A notebook In Java
pages already written length(): characters stored
blank pages left capacity(): room before it must grow (16 for an empty builder)
full? move to a thicker notebook, copying once grows automatically, to about double + 2
public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        System.out.println(sb.length() + " / " + sb.capacity());
        sb.append("Registration desk");
        System.out.println(sb.length() + " / " + sb.capacity());
        StringBuilder big = new StringBuilder(1000);
        System.out.println(big.length() + " / " + big.capacity());
    }
}
0 / 16
17 / 34
0 / 1000

17 characters didn't fit in 16, so the builder grew to 16 × 2 + 2 = 34. Growing copies the text, but only now and then (each time the room doubles), not on every append like String +. If you know roughly how big the text will be, ask for that capacity up front: new StringBuilder(1000).

StringBuffer: the synchronized twin

A thread is one job a program runs at the same time as other jobs (like two gate scanners writing to one log). We build threads later in the course.

public class Main {
    public static void main(String[] args) {
        StringBuffer log = new StringBuffer();
        log.append("Gate 1 open").append(" | ").append("Gate 2 open");
        log.reverse().reverse();
        System.out.println(log + " | length " + log.length());
    }
}
Gate 1 open | Gate 2 open | length 25
String StringBuilder StringBuffer
can change? no (immutable) yes (mutable) yes (mutable)
thread safe? yes (it can't change) no yes (synchronized)
speed for many edits slow fastest fast, a little slower
since Java 1.0 Java 5 Java 1.0
use for fixed text, a few joins building text, loops text shared by many threads

Note: for a single line like "Hi " + name + "!", the compiler already does the joining efficiently. StringBuilder matters when you add in a loop or edit text many times.

Common mistakes (and the real error messages)

Mistake Real message Why Fix
s = s + i; in a big loop no error, just very slow every + copies the whole String sb.append(i);
String name = sb; incompatible types: StringBuilder cannot be converted to String different types sb.toString()
a.equals(b) on two builders no error, false compares objects a.toString().equals(b.toString()) or a.compareTo(b) == 0
sb.charAt(10) when length is 4 StringIndexOutOfBoundsException: Index 10 out of bounds for length 4 indexes go 0 to length() - 1 check sb.length()
StringBuilder copy = order; to copy no error, both change together copies the reference new StringBuilder(order)
sb.reverse() expecting the original kept no error, the builder is reversed builders change themselves reverse a copy

Mini project: chai stall bill

public class Main {
    public static void main(String[] args) {
        String[] items = {"Masala chai", "Samosa", "Bun maska"};
        int[] qty = {2, 3, 1};
        int[] price = {20, 15, 30};
        StringBuilder bill = new StringBuilder("--- Raju Chai Stall ---\n");
        int total = 0;
        for (int i = 0; i < items.length; i++) {
            int cost = qty[i] * price[i];
            total += cost;
            bill.append(items[i]).append(" x").append(qty[i]).append(" = Rs ").append(cost).append("\n");
        }
        bill.append("Total: Rs ").append(total);
        System.out.println(bill);
    }
}
--- Raju Chai Stall ---
Masala chai x2 = Rs 40
Samosa x3 = Rs 45
Bun maska x1 = Rs 30
Total: Rs 115

\n is the new line escape: it ends the line inside the text. Extend it: add 5% GST on the total, and a "Thank you, visit again!" line.

Practice

  1. Build 1-2-3-...-20 with no dash at the end.
  2. Remove all spaces from a sentence using deleteCharAt.
  3. Hide an email with setCharAt: priya@gmail.com → p****@gmail.com.
  4. Time String + vs StringBuilder for 20,000 rounds on your machine.
  5. Reverse each word of a sentence but keep the word order: chai is hot → iahc si toh.
Solutions and hints
  1. java StringBuilder sb = new StringBuilder(); for (int i = 1; i <= 20; i++) { sb.append(i); if (i < 20) { sb.append("-"); } } System.out.println(sb); (Or append "-" every time and sb.deleteCharAt(sb.length() - 1) at the end.)
  2. Walk backwards, so deleting doesn't shift the characters you haven't checked yet: java StringBuilder sb = new StringBuilder("chai is hot"); for (int i = sb.length() - 1; i >= 0; i--) { if (sb.charAt(i) == ' ') { sb.deleteCharAt(i); } } System.out.println(sb); // chaiishot
  3. java StringBuilder e = new StringBuilder("priya@gmail.com"); for (int i = 1; i < e.indexOf("@"); i++) { e.setCharAt(i, '*'); } System.out.println(e); // p****@gmail.com
  4. Copy the two timed programs above and change 50000 to 20000.
  5. java StringBuilder out = new StringBuilder(); for (String w : "chai is hot".split(" ")) { out.append(new StringBuilder(w).reverse()).append(" "); } System.out.println(out.toString().strip()); // iahc si toh

Quick quiz

What does this print?

StringBuilder sb = new StringBuilder("abc");
sb.append("d").reverse();
System.out.println(sb + " " + sb.length());
Answer

B. dcba 4. A StringBuilder changes itself: append("d") makes it abcd, and reverse() flips the same builder to dcba. Nothing needs to be stored. Compare with episode 9's quiz, where s.toUpperCase() changed nothing.

Interview questions

  1. String vs StringBuilder vs StringBuffer? String is immutable; StringBuilder is mutable and not synchronized (fastest); StringBuffer is mutable and synchronized (thread safe).
  2. Why is String concatenation in a loop slow? Each + creates a new String and copies all previous characters: O(n²) total work.
  3. What is the default capacity of a StringBuilder, and how does it grow? 16; when full it grows to about (old × 2) + 2.
  4. Does StringBuilder.equals compare content? No; it's inherited from Object and compares references.
  5. Which was added later, StringBuilder or StringBuffer, and why? StringBuilder, in Java 5, as a faster unsynchronized version for the common single-thread case.
  6. Is "a" + b + "c" on one line slow? No; the compiler handles single expressions efficiently. The problem is repeated += in a loop.
  7. How do you convert a StringBuilder to a String? sb.toString() (or String.valueOf(sb)).

Glossary

Term Meaning Example
mutable can be changed after it's created a StringBuilder
immutable can never change after it's created a String
StringBuilder Java's mutable text class, not synchronized new StringBuilder()
StringBuffer the same as StringBuilder, but synchronized new StringBuffer()
append add to the end sb.append(5)
method chaining calling the next method on the returned object sb.append(a).append(b)
toString() turns a builder (or any object) into a String sb.toString()
capacity how many characters a builder can hold before it grows 16 at first
millisecond one thousandth of a second 1405 ms ≈ 1.4 s
System.currentTimeMillis() the current time in milliseconds, as a long stopwatch start/end
thread a job the program runs at the same time as others two gate scanners
thread safe works correctly when many threads use it at once StringBuffer
synchronized locked so only one thread runs it at a time StringBuffer's methods
reference a variable's link to an object copy = order
\n the new line escape inside text "Total\n"
palindrome a word that reads the same backwards madam

Summary

Next episode: Methods: write the steps once, give them a name, use them everywhere. Plus pass-by-value and overloading.

← Episode 9Java Strings: Immutability, the String Pool, equals vs == and String Methods