Java Tutorial
🔍

Java StringBuilder

Java StringBuilder

Every time you use + to concatenate strings in a loop, Java creates a new String object on each iteration. Ten iterations — ten new objects. A million iterations — a million discarded objects that the garbage collector must clean up. For a few concatenations in simple code, this cost is invisible. In loops, large data processing, or response builders, it becomes a measurable performance problem.

StringBuilder solves this. It maintains a mutable character buffer that grows as you add text. You append, insert, delete, and modify directly inside the same buffer. Only when you call toString() does a single String object get created. One buffer, one String — not one String per concatenation.

What Is StringBuilder?

StringBuilder is a mutable sequence of characters in the java.lang package. Unlike String, which is immutable and creates a new object for every modification, StringBuilder modifies its internal buffer in place.

String concatenation with +:

  step 1: result = "Order"                → creates "Order" object
  step 2: result = result + " ID:"        → creates "Order ID:" object (old discarded)
  step 3: result = result + " ORD-001"    → creates "Order ID: ORD-001" object
  step 4: result = result + " | Status:"  → creates new object
  step 5: result = result + " PLACED"     → creates new object
          ▲
          5 String objects created — 4 discarded immediately

StringBuilder approach:

  sb = new StringBuilder(64)       → one buffer allocated
  sb.append("Order")               → writes into buffer
  sb.append(" ID:")                → extends buffer content
  sb.append(" ORD-001")           → extends buffer content
  sb.append(" | Status:")         → extends buffer content
  sb.append(" PLACED")            → extends buffer content
  result = sb.toString()           → one String object created at the end
  ▲
  1 buffer, 1 final String — 0 intermediate objects discarded

Creating a StringBuilder

// File: StringBuilderCreation.java public class StringBuilderCreation { public static void main(String[] args) { // Way 1 — empty builder (default initial capacity = 16 chars) StringBuilder sb1 = new StringBuilder(); System.out.println("Empty capacity: " + sb1.capacity()); // 16 System.out.println("Empty length : " + sb1.length()); // 0 // Way 2 — with initial capacity (use when final size is roughly known) StringBuilder sb2 = new StringBuilder(64); System.out.println("Cap 64 capacity: " + sb2.capacity()); // 64 // Way 3 — with initial content StringBuilder sb3 = new StringBuilder("Hello"); System.out.println("Content : " + sb3); // Hello System.out.println("Content length : " + sb3.length()); // 5 System.out.println("Content capacity: " + sb3.capacity()); // 16 + 5 = 21 // When capacity is exceeded, it doubles + 2 StringBuilder sb4 = new StringBuilder(4); // capacity 4 sb4.append("Hello World"); // 11 chars > 4 capacity System.out.println("Auto-grown capacity: " + sb4.capacity()); // (4+2)*2 = 12 or larger } }
Output:
Empty  capacity: 16
Empty  length  : 0
Cap 64 capacity: 64
Content        : Hello
Content length : 5
Content capacity: 21
Auto-grown capacity: 12

Setting an appropriate initial capacity avoids repeated internal array resizing. If you know the output will be around 500 characters, new StringBuilder(512) prevents multiple resize operations during building.

append() — Adding Content

append() is the most used method. It adds content to the end of the buffer. It is heavily overloaded — you can append String, int, double, char, boolean, char[], StringBuilder, and any Object (via its toString()).

// File: AppendDemo.java public class AppendDemo { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); // append() — all the types it accepts sb.append("Name: "); // String sb.append("Priya"); sb.append('\n'); // char sb.append("Age: "); sb.append(24); // int sb.append('\n'); sb.append("Salary: Rs."); sb.append(75000.50); // double sb.append('\n'); sb.append("Active: "); sb.append(true); // boolean sb.append('\n'); System.out.println(sb.toString()); // Method chaining — append returns the same StringBuilder StringBuilder address = new StringBuilder() .append("14, ") .append("Koramangala 4th Block, ") .append("Bengaluru") .append(" - ") .append(560034); // int appended directly System.out.println("Address: " + address); System.out.println(); // Practical use — building a CSV row String[] fields = {"ORD-001", "Priya Sharma", "1299.50", "PLACED", "2024-01-15"}; StringBuilder csv = new StringBuilder(); for (int i = 0; i < fields.length; i++) { csv.append(fields[i]); if (i < fields.length - 1) csv.append(","); } System.out.println("CSV: " + csv); System.out.println(); // Append a char array char[] chars = {'J', 'a', 'v', 'a'}; StringBuilder charAppend = new StringBuilder("Language: "); charAppend.append(chars); System.out.println(charAppend); } }
Output:
Name: Priya
Age: 24
Salary: Rs.75000.5
Active: true

Address: 14, Koramangala 4th Block, Bengaluru - 560034

CSV: ORD-001,Priya Sharma,1299.50,PLACED,2024-01-15

Language: Java

append() returns this — the same StringBuilder object — which is what makes chaining possible. Each chained call modifies the same buffer and returns it for the next call.

insert() — Adding Content at a Position

insert(offset, value) inserts content at the specified position. All existing characters from that position onward shift right.

// File: InsertDemo.java public class InsertDemo { public static void main(String[] args) { StringBuilder sb = new StringBuilder("Hello World"); System.out.println("Original : " + sb); System.out.println("Length : " + sb.length()); // Insert a String at index 5 (between "Hello" and " World") sb.insert(5, ","); System.out.println("After insert(5, ',') : " + sb); // "Hello, World" // Insert at the beginning (index 0) sb.insert(0, ">>> "); System.out.println("After insert(0, '>>> '): " + sb); // ">>> Hello, World" // Insert at the end (length = insert at end) sb.insert(sb.length(), " <<<"); System.out.println("After insert at end : " + sb); // ">>> Hello, World <<<" System.out.println(); // Practical use — inserting a formatted timestamp into a log message StringBuilder log = new StringBuilder("[INFO] Server started on port 8080"); String timestamp = "[2024-01-15 10:30:00] "; log.insert(0, timestamp); System.out.println("Log with timestamp: " + log); System.out.println(); // Insert various types StringBuilder data = new StringBuilder("Amount: "); data.insert(data.length(), 45999); // int data.insert(0, "Product: Laptop | "); // String at start System.out.println(data); } }
Output:
Original  : Hello World
Length    : 11
After insert(5, ',') : Hello, World
After insert(0, '>>> '): >>> Hello, World
After insert at end    : >>> Hello, World <<<

Log with timestamp: [2024-01-15 10:30:00] [INFO] Server started on port 8080

Product: Laptop | Amount: 45999

delete() and deleteCharAt() — Removing Content

delete(start, end) removes characters from start (inclusive) to end (exclusive). deleteCharAt(index) removes a single character at the given index.

// File: DeleteDemo.java public class DeleteDemo { public static void main(String[] args) { StringBuilder sb = new StringBuilder("Hello, Beautiful World!"); System.out.println("Original: " + sb); // delete(start, end) — removes from start (inclusive) to end (exclusive) sb.delete(7, 17); // removes "Beautiful " (indices 7 to 16) System.out.println("After delete(7,17) : " + sb); // "Hello, World!" // deleteCharAt(index) — removes a single character sb.deleteCharAt(5); // removes the comma at index 5 System.out.println("After deleteCharAt(5): " + sb); // "Hello World!" System.out.println(); // Practical use — remove surrounding quotes from a JSON value StringBuilder json = new StringBuilder("\"Bengaluru\""); System.out.println("With quotes : " + json); json.deleteCharAt(json.length() - 1); // remove trailing quote json.deleteCharAt(0); // remove leading quote System.out.println("Without quotes: " + json); // Bengaluru System.out.println(); // Remove the last comma in a list builder StringBuilder list = new StringBuilder(); String[] items = {"Laptop", "Mouse", "Keyboard", "Monitor"}; for (String item : items) { list.append(item).append(", "); } System.out.println("Before cleanup: " + list); // Remove trailing ", " (last 2 characters) list.delete(list.length() - 2, list.length()); System.out.println("After cleanup : " + list); } }
Output:
Original: Hello, Beautiful World!
After delete(7,17)  : Hello, World!
After deleteCharAt(5): Hello World!

With quotes   : "Bengaluru"
Without quotes: Bengaluru

Before cleanup: Laptop, Mouse, Keyboard, Monitor, 
After cleanup : Laptop, Mouse, Keyboard, Monitor

The trailing comma removal pattern — build with separators, then delete the last one — is extremely common. An alternative is to check if (i > 0) before appending the separator, but the delete approach often reads more cleanly.

replace() — Replacing a Range

replace(start, end, newString) replaces characters from start (inclusive) to end (exclusive) with newString. The new string can be shorter, longer, or the same length.

// File: ReplaceDemo.java public class ReplaceDemo { public static void main(String[] args) { StringBuilder sb = new StringBuilder("Hello, Java World!"); System.out.println("Original: " + sb); // replace(start, end, newStr) — replaces a range with new content sb.replace(7, 11, "Python"); // replaces "Java" with "Python" System.out.println("After replace : " + sb); // "Hello, Python World!" System.out.println(); // Masking sensitive data — replace middle digits of a phone number StringBuilder phone = new StringBuilder("9876543210"); phone.replace(3, 7, "****"); // mask digits 3 through 6 System.out.println("Masked phone: " + phone); // "987****210" System.out.println(); // Replace shorter with longer — buffer expands StringBuilder msg = new StringBuilder("Hi!"); System.out.println("Before: '" + msg + "' length=" + msg.length()); msg.replace(0, 2, "Hello"); System.out.println("After : '" + msg + "' length=" + msg.length()); System.out.println(); // Practical use — sanitise a template message StringBuilder template = new StringBuilder( "Dear [NAME], your order [ORDER_ID] of Rs.[AMOUNT] is [STATUS]."); template.replace(template.indexOf("[NAME]"), template.indexOf("[NAME]") + 6, "Priya"); template.replace(template.indexOf("[ORDER_ID]"),template.indexOf("[ORDER_ID]")+ 10,"ORD-2024-001"); template.replace(template.indexOf("[AMOUNT]"), template.indexOf("[AMOUNT]") + 8, "1299.50"); template.replace(template.indexOf("[STATUS]"), template.indexOf("[STATUS]") + 8, "PLACED"); System.out.println("Filled template: " + template); } }
Output:
Original: Hello, Java World!
After replace : Hello, Python World!

Masked phone: 987****210

Before: 'Hi!' length=3
After : 'Hello!' length=6

Filled template: Dear Priya, your order ORD-2024-001 of Rs.1299.50 is PLACED.

reverse() — Reversing the Content

reverse() reverses the entire sequence of characters in the buffer. It modifies in place and returns this.

// File: ReverseDemo.java public class ReverseDemo { public static void main(String[] args) { // Basic reverse StringBuilder sb = new StringBuilder("Hello World"); System.out.println("Original : " + sb); sb.reverse(); System.out.println("Reversed : " + sb); System.out.println(); // Check palindrome using reverse() String[] words = {"racecar", "madam", "hello", "level", "Java"}; System.out.println("Palindrome check:"); for (String word : words) { String reversed = new StringBuilder(word).reverse().toString(); boolean isPalindrome = word.equalsIgnoreCase(reversed); System.out.printf(" %-10s → %s%n", word, isPalindrome ? "palindrome" : "not palindrome"); } System.out.println(); // Reverse words in a sentence (not characters) String sentence = "Java is awesome"; String[] wordsArr = sentence.split(" "); StringBuilder reversed = new StringBuilder(); for (int i = wordsArr.length - 1; i >= 0; i--) { reversed.append(wordsArr[i]); if (i > 0) reversed.append(" "); } System.out.println("Original sentence : " + sentence); System.out.println("Reversed words : " + reversed); } }
Output:
Original : Hello World
Reversed : dlroW olleH

Palindrome check:
  racecar    → palindrome
  madam      → palindrome
  hello      → not palindrome
  level      → palindrome
  Java       → not palindrome

Original sentence : Java is awesome
Reversed words    : awesome is Java

indexOf(), lastIndexOf(), charAt(), setCharAt()

These methods let you search inside and modify individual characters of the buffer.

// File: SearchModifyDemo.java public class SearchModifyDemo { public static void main(String[] args) { StringBuilder sb = new StringBuilder("Java is great. Java is powerful."); // indexOf() — first occurrence System.out.println("indexOf('Java') : " + sb.indexOf("Java")); // 0 System.out.println("indexOf('Java', 5) : " + sb.indexOf("Java", 5)); // 15 System.out.println("lastIndexOf('Java') : " + sb.lastIndexOf("Java")); // 15 System.out.println("indexOf('Python') : " + sb.indexOf("Python")); // -1 System.out.println(); // charAt() — read a character at position System.out.println("charAt(0) : " + sb.charAt(0)); // J System.out.println("charAt(5) : " + sb.charAt(5)); // s // setCharAt() — modify a single character System.out.println("\nBefore setCharAt: " + sb); sb.setCharAt(0, 'j'); // change 'J' to 'j' System.out.println("After setCharAt(0,'j'): " + sb); System.out.println(); // Practical use — capitalise first letter of each sentence StringBuilder text = new StringBuilder("hello world. this is java. it is fun."); text.setCharAt(0, Character.toUpperCase(text.charAt(0))); int dotPos = text.indexOf(". "); while (dotPos != -1 && dotPos + 2 < text.length()) { text.setCharAt(dotPos + 2, Character.toUpperCase(text.charAt(dotPos + 2))); dotPos = text.indexOf(". ", dotPos + 1); } System.out.println("Capitalised: " + text); } }
Output:
indexOf('Java')      : 0
indexOf('Java', 5)   : 15
lastIndexOf('Java')  : 15
indexOf('Python')    : -1

charAt(0)  : J
charAt(5)  : s

Before setCharAt: Java is great. Java is powerful.
After setCharAt(0,'j'): java is great. java is powerful.

Capitalised: Hello world. This is java. It is fun.

substring() — Extracting Content

substring(start) and substring(start, end) extract a portion of the buffer and return a new String. The buffer itself is unchanged.

// File: SubstringBuilderDemo.java public class SubstringBuilderDemo { public static void main(String[] args) { StringBuilder sb = new StringBuilder("Order: ORD-2024-001 | Customer: Priya | Amount: Rs.1299"); // substring(start) — from position to end System.out.println("From index 7 : " + sb.substring(7)); // substring(start, end) — from start inclusive to end exclusive System.out.println("Order ID : " + sb.substring(7, 19)); // ORD-2024-001 // Find and extract dynamically int custStart = sb.indexOf("Customer: ") + 10; int custEnd = sb.indexOf(" |", custStart); if (custEnd == -1) custEnd = sb.length(); System.out.println("Customer name : " + sb.substring(custStart, custEnd)); System.out.println(); // length() — current number of characters System.out.println("Buffer length: " + sb.length()); // capacity() — current internal buffer size System.out.println("Buffer capacity: " + sb.capacity()); } }
Output:
From index 7  : ORD-2024-001 | Customer: Priya | Amount: Rs.1299
Order ID      : ORD-2024-001
Customer name : Priya

Buffer length: 55
Buffer capacity: 71

String vs StringBuilder vs StringBuffer — Comparison Table

AspectStringStringBuilderStringBuffer
MutableNo — immutableYes — mutable in placeYes — mutable in place
Thread-safeYes — immutable is safeNo — not synchronisedYes — all methods synchronised
Performance in loopsPoor — creates new object per concatFast — single buffer throughoutSlower than StringBuilder — sync overhead
MemoryMany short-lived objectsOne buffer, one final StringOne buffer, one final String
When to useFixed values, constants, comparisonsBuilding strings in loops — single threadBuilding strings across multiple threads
+ operatorYesNo — use append()No — use append()
Return type of methodsNew StringSame StringBuilder (chainable)Same StringBuffer (chainable)
IntroducedJava 1.0Java 1.5Java 1.0
Packagejava.langjava.langjava.lang
Main methodstrim(), split(), replace() (returns new)append(), insert(), delete(), reverse()Same as StringBuilder

Performance Comparison — + vs StringBuilder

// File: PerformanceDemo.java public class PerformanceDemo { public static void main(String[] args) { int iterations = 100_000; // Method 1 — String concatenation with + long start1 = System.currentTimeMillis(); String result1 = ""; for (int i = 0; i < iterations; i++) { result1 = result1 + i + ","; } long time1 = System.currentTimeMillis() - start1; System.out.println("String + : " + time1 + " ms (length: " + result1.length() + ")"); // Method 2 — StringBuilder long start2 = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < iterations; i++) { sb.append(i).append(","); } String result2 = sb.toString(); long time2 = System.currentTimeMillis() - start2; System.out.println("StringBuilder : " + time2 + " ms (length: " + result2.length() + ")"); System.out.println(); if (time1 > 0 && time2 > 0) { System.out.println("StringBuilder was ~" + (time1 / Math.max(time2, 1)) + "x faster"); } else { System.out.println("StringBuilder was significantly faster"); } } }
Output:
String +      : 2847 ms  (length: 488894)
StringBuilder : 8 ms     (length: 488894)

StringBuilder was ~355x faster

For 100,000 concatenations, StringBuilder is hundreds of times faster. The + operator creates a new String on every iteration — each one copies all previous content into a new array. StringBuilder writes into the same growing buffer — only the new content is copied each time.

Basic Example

public class Main { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); String[] items = {"Apple", "Banana", "Cherry"}; for (int i = 0; i < items.length; i++) { sb.append(items[i]); if (i < items.length - 1) { sb.append(", "); } } String result = sb.toString(); System.out.println("Items : " + result); System.out.println("Length: " + result.length()); } }

Output

Items : Apple, Banana, Cherry
Length: 21

How it works:

  1. ›sb starts as an empty, mutable buffer.
  2. ›The loop calls append() for each item, writing directly into the same buffer instead of creating a new object every time.
  3. ›A comma and space are appended between items, but not after the last one.
  4. ›sb.toString() is called once, at the end, to produce the final String — this is far more efficient than using + inside the loop.

Best Practices

Use StringBuilder inside any loop that builds a string. Even a loop with just ten iterations benefits from StringBuilder. The performance gap grows with iteration count — for loops processing thousands or millions of records, StringBuilder is not optional.

Set initial capacity when the final size is roughly predictable. new StringBuilder(capacity) pre-allocates the internal array. Without it, StringBuilder starts at 16 characters and doubles each time it runs out. For emails, reports, or SQL statements of predictable size, pre-allocating avoids multiple internal copy operations.

Use toString() only once — at the very end. Every toString() call creates a new String object from the buffer's content. Calling it inside a loop or multiple times within one build operation wastes memory. Build everything first, then convert once.

Chain append() calls for readability when building in sequence. sb.append("Name: ").append(name).append(" | Age: ").append(age) is more readable than multiple separate statements and produces identical bytecode.

Common Mistakes

Mistake 1 — Converting to String Inside a Loop

StringBuilder sb = new StringBuilder(); for (String word : words) { sb.append(word).append(" "); String partial = sb.toString(); // bad — creates String on every iteration System.out.println(partial); // use sb.toString() only once at the end } // If you need to print inside the loop: for (String word : words) { sb.append(word).append(" "); System.out.println(sb); // println calls toString() — acceptable for debugging }

Mistake 2 — Using StringBuilder When String Is Enough

// Over-engineering — only two parts, no loop, no modification needed StringBuilder sb = new StringBuilder(); sb.append("Hello, "); sb.append(name); String greeting = sb.toString(); // Simpler — + is fine for a small fixed number of concatenations String greeting = "Hello, " + name;

+ for two or three fixed concatenations outside a loop is perfectly fine. The compiler optimises such expressions into StringBuilder internally anyway. Reserve explicit StringBuilder for loops, dynamic building, and frequent modifications.

Mistake 3 — Confusing StringBuilder.replace() With String.replace()

// String.replace() — replaces ALL occurrences of a substring String result = "Java Java Java".replace("Java", "Python"); // "Python Python Python" // StringBuilder.replace() — replaces a CHARACTER RANGE by indices StringBuilder sb = new StringBuilder("Java Java Java"); sb.replace(5, 9, "Python"); // replaces chars at index 5-8 with "Python" System.out.println(sb); // "Java Python Java" — only the range, not all occurrences

String.replace() replaces all occurrences of a literal substring. StringBuilder.replace() replaces a range specified by start and end indices. They are fundamentally different operations with the same method name.

Mistake 4 — Not Removing the Trailing Separator

StringBuilder sb = new StringBuilder(); for (String item : items) { sb.append(item).append(", "); // always appends ", " } System.out.println(sb); // "Laptop, Mouse, Keyboard, " ← trailing ", " // Fix — delete the last two characters if (sb.length() >= 2) { sb.delete(sb.length() - 2, sb.length()); } // Alternative fix — check before appending separator for (int i = 0; i < items.length; i++) { if (i > 0) sb.append(", "); sb.append(items[i]); }

Interview Questions

Q1. What is StringBuilder and why is it preferred over String concatenation in loops?

StringBuilder is a mutable character buffer in java.lang that supports efficient in-place string building. When you concatenate strings using + in a loop, each iteration creates a new String object — all previous content is copied into each new object. For N iterations, this creates O(N²) total character copies. StringBuilder maintains a single buffer throughout, appending to it directly. Only the final toString() call creates a String. For N appends, only O(N) total characters are copied.

Q2. What is the difference between StringBuilder and StringBuffer?

Both are mutable and have the same methods. The key difference is thread safety: StringBuffer synchronises every method call, making it safe for use across multiple threads but slower. StringBuilder is not synchronised, making it faster for single-threaded use. In modern Java, most string building happens in a single thread — local variables in a method are not shared between threads. For these cases, StringBuilder is the recommended choice. Use StringBuffer only when the same mutable string object is genuinely shared and modified by multiple threads simultaneously.

Q3. What does the capacity() method return in StringBuilder?

capacity() returns the current size of the internal character array allocated for the buffer. It is always greater than or equal to length(). When content is appended that would exceed the current capacity, StringBuilder allocates a new larger array (typically oldCapacity * 2 + 2) and copies the existing content. length() returns the actual number of characters currently in the buffer — always less than or equal to capacity(). Setting an appropriate initial capacity with new StringBuilder(n) avoids these resize operations.

Q4. Does StringBuilder have a replace() method and how does it differ from String.replace()?

Yes. StringBuilder.replace(start, end, newStr) replaces the characters in the buffer from start (inclusive) to end (exclusive) with newStr. It replaces a positional range — not all occurrences of a substring. String.replace(target, replacement) replaces all occurrences of a literal substring throughout the entire string. They address completely different needs: StringBuilder.replace() for positional modification, String.replace() for global substitution.

Q5. Is StringBuilder thread-safe? What should you use when building strings across multiple threads?

StringBuilder is not thread-safe. If two threads simultaneously call append() or other modifying methods on the same StringBuilder, the result is unpredictable — characters can interleave or the internal state can become corrupt. For multi-threaded scenarios, use StringBuffer which synchronises all methods. Alternatively — and often better — keep string building within a single thread and use a thread pool where each thread has its own StringBuilder.

Q6. At what point should you prefer String over StringBuilder?

Use String for fixed values, constants, method parameters that are not built in loops, and all comparisons. String's immutability makes it safe to pass between methods and threads without defensive copying. Use StringBuilder when you are building a string iteratively — especially in loops — or when you need to insert, delete, replace, or reverse parts of a string in place. The compiler automatically converts simple multi-part + expressions outside loops into StringBuilder internally, so explicit StringBuilder is mainly needed for loops and dynamic construction.

FAQs

Can StringBuilder hold null values?

sb.append(null) appends the string "null" to the buffer — it does not throw an exception. insert(offset, null) also appends "null". This is different from String.valueOf(null) which also returns "null". If you do not want nulls appended, add an explicit null check before calling append().

What happens when StringBuilder runs out of capacity?

When a append() or insert() call would exceed the current capacity, StringBuilder automatically allocates a new internal array of size max(newLength, oldCapacity * 2 + 2), copies all existing content, and continues. This is transparent to the caller. Setting an appropriate initial capacity with new StringBuilder(n) avoids this resizing overhead, especially in performance-critical code.

Is it safe to share a StringBuilder between methods?

Passing a StringBuilder to a method follows the same pass-by-value-of-reference rules as any object. The method receives a reference to the same buffer. Any append(), insert(), delete(), or reverse() call in the method modifies the shared buffer and is visible to the caller. If a method should read the content without modifying it, extract the String first with toString() and pass the String instead.

Can you convert StringBuilder back to a char array?

Yes. sb.toString().toCharArray() converts to a char[]. There is no direct StringBuilder.toCharArray() method — you must go through toString() first. Alternatively, use sb.charAt(i) in a loop to read individual characters from the buffer without creating an intermediate String.

Does the + operator use StringBuilder internally?

Yes, for compile-time expressions. When the Java compiler sees "Hello, " + name + "!" outside a loop, it generates bytecode equivalent to new StringBuilder().append("Hello, ").append(name).append("!").toString(). However, the compiler does NOT perform this optimisation across loop iterations — each iteration of result = result + item still creates a new String. This is why explicit StringBuilder is needed for loops despite the compiler optimisation for simple expressions.

Summary

StringBuilder is the right tool whenever you need to build a String dynamically — especially inside loops. It maintains a single mutable buffer from the first character to the last, creating only one String object when toString() is called at the end. The performance difference against + in loops grows from negligible to hundreds of times faster as iteration counts increase.

The methods you will use most: append() for adding content at the end, insert() for adding at a position, delete() and deleteCharAt() for removal, replace() for range substitution, reverse() for reversing, and indexOf() for searching. All modifying methods return this, enabling clean method chaining.

For interviews, be ready to explain the performance difference with a loop example, describe the difference between StringBuilder and StringBuffer (thread safety), explain what capacity() returns versus length(), and demonstrate the trailing separator removal pattern.

What to Read Next

Practice Programs

Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.

Browse all Java programs →