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
1// File: StringBuilderCreation.java
2
3public class StringBuilderCreation {
4
5 public static void main(String[] args) {
6
7 // Way 1 — empty builder (default initial capacity = 16 chars)
8 StringBuilder sb1 = new StringBuilder();
9 System.out.println("Empty capacity: " + sb1.capacity()); // 16
10 System.out.println("Empty length : " + sb1.length()); // 0
11
12 // Way 2 — with initial capacity (use when final size is roughly known)
13 StringBuilder sb2 = new StringBuilder(64);
14 System.out.println("Cap 64 capacity: " + sb2.capacity()); // 64
15
16 // Way 3 — with initial content
17 StringBuilder sb3 = new StringBuilder("Hello");
18 System.out.println("Content : " + sb3); // Hello
19 System.out.println("Content length : " + sb3.length()); // 5
20 System.out.println("Content capacity: " + sb3.capacity()); // 16 + 5 = 21
21
22 // When capacity is exceeded, it doubles + 2
23 StringBuilder sb4 = new StringBuilder(4); // capacity 4
24 sb4.append("Hello World"); // 11 chars > 4 capacity
25 System.out.println("Auto-grown capacity: " + sb4.capacity()); // (4+2)*2 = 12 or larger
26 }
27}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()).
1// File: AppendDemo.java
2
3public class AppendDemo {
4
5 public static void main(String[] args) {
6
7 StringBuilder sb = new StringBuilder();
8
9 // append() — all the types it accepts
10 sb.append("Name: "); // String
11 sb.append("Priya");
12 sb.append('\n'); // char
13 sb.append("Age: ");
14 sb.append(24); // int
15 sb.append('\n');
16 sb.append("Salary: Rs.");
17 sb.append(75000.50); // double
18 sb.append('\n');
19 sb.append("Active: ");
20 sb.append(true); // boolean
21 sb.append('\n');
22
23 System.out.println(sb.toString());
24
25 // Method chaining — append returns the same StringBuilder
26 StringBuilder address = new StringBuilder()
27 .append("14, ")
28 .append("Koramangala 4th Block, ")
29 .append("Bengaluru")
30 .append(" - ")
31 .append(560034); // int appended directly
32
33 System.out.println("Address: " + address);
34
35 System.out.println();
36
37 // Practical use — building a CSV row
38 String[] fields = {"ORD-001", "Priya Sharma", "1299.50", "PLACED", "2024-01-15"};
39 StringBuilder csv = new StringBuilder();
40 for (int i = 0; i < fields.length; i++) {
41 csv.append(fields[i]);
42 if (i < fields.length - 1) csv.append(",");
43 }
44 System.out.println("CSV: " + csv);
45
46 System.out.println();
47
48 // Append a char array
49 char[] chars = {'J', 'a', 'v', 'a'};
50 StringBuilder charAppend = new StringBuilder("Language: ");
51 charAppend.append(chars);
52 System.out.println(charAppend);
53 }
54}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.
1// File: InsertDemo.java
2
3public class InsertDemo {
4
5 public static void main(String[] args) {
6
7 StringBuilder sb = new StringBuilder("Hello World");
8 System.out.println("Original : " + sb);
9 System.out.println("Length : " + sb.length());
10
11 // Insert a String at index 5 (between "Hello" and " World")
12 sb.insert(5, ",");
13 System.out.println("After insert(5, ',') : " + sb); // "Hello, World"
14
15 // Insert at the beginning (index 0)
16 sb.insert(0, ">>> ");
17 System.out.println("After insert(0, '>>> '): " + sb); // ">>> Hello, World"
18
19 // Insert at the end (length = insert at end)
20 sb.insert(sb.length(), " <<<");
21 System.out.println("After insert at end : " + sb); // ">>> Hello, World <<<"
22
23 System.out.println();
24
25 // Practical use — inserting a formatted timestamp into a log message
26 StringBuilder log = new StringBuilder("[INFO] Server started on port 8080");
27 String timestamp = "[2024-01-15 10:30:00] ";
28 log.insert(0, timestamp);
29 System.out.println("Log with timestamp: " + log);
30
31 System.out.println();
32
33 // Insert various types
34 StringBuilder data = new StringBuilder("Amount: ");
35 data.insert(data.length(), 45999); // int
36 data.insert(0, "Product: Laptop | "); // String at start
37 System.out.println(data);
38 }
39}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.
1// File: DeleteDemo.java
2
3public class DeleteDemo {
4
5 public static void main(String[] args) {
6
7 StringBuilder sb = new StringBuilder("Hello, Beautiful World!");
8 System.out.println("Original: " + sb);
9
10 // delete(start, end) — removes from start (inclusive) to end (exclusive)
11 sb.delete(7, 17); // removes "Beautiful " (indices 7 to 16)
12 System.out.println("After delete(7,17) : " + sb); // "Hello, World!"
13
14 // deleteCharAt(index) — removes a single character
15 sb.deleteCharAt(5); // removes the comma at index 5
16 System.out.println("After deleteCharAt(5): " + sb); // "Hello World!"
17
18 System.out.println();
19
20 // Practical use — remove surrounding quotes from a JSON value
21 StringBuilder json = new StringBuilder("\"Bengaluru\"");
22 System.out.println("With quotes : " + json);
23 json.deleteCharAt(json.length() - 1); // remove trailing quote
24 json.deleteCharAt(0); // remove leading quote
25 System.out.println("Without quotes: " + json); // Bengaluru
26
27 System.out.println();
28
29 // Remove the last comma in a list builder
30 StringBuilder list = new StringBuilder();
31 String[] items = {"Laptop", "Mouse", "Keyboard", "Monitor"};
32 for (String item : items) {
33 list.append(item).append(", ");
34 }
35 System.out.println("Before cleanup: " + list);
36
37 // Remove trailing ", " (last 2 characters)
38 list.delete(list.length() - 2, list.length());
39 System.out.println("After cleanup : " + list);
40 }
41}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.
1// File: ReplaceDemo.java
2
3public class ReplaceDemo {
4
5 public static void main(String[] args) {
6
7 StringBuilder sb = new StringBuilder("Hello, Java World!");
8 System.out.println("Original: " + sb);
9
10 // replace(start, end, newStr) — replaces a range with new content
11 sb.replace(7, 11, "Python"); // replaces "Java" with "Python"
12 System.out.println("After replace : " + sb); // "Hello, Python World!"
13
14 System.out.println();
15
16 // Masking sensitive data — replace middle digits of a phone number
17 StringBuilder phone = new StringBuilder("9876543210");
18 phone.replace(3, 7, "****"); // mask digits 3 through 6
19 System.out.println("Masked phone: " + phone); // "987****210"
20
21 System.out.println();
22
23 // Replace shorter with longer — buffer expands
24 StringBuilder msg = new StringBuilder("Hi!");
25 System.out.println("Before: '" + msg + "' length=" + msg.length());
26 msg.replace(0, 2, "Hello");
27 System.out.println("After : '" + msg + "' length=" + msg.length());
28
29 System.out.println();
30
31 // Practical use — sanitise a template message
32 StringBuilder template = new StringBuilder(
33 "Dear [NAME], your order [ORDER_ID] of Rs.[AMOUNT] is [STATUS].");
34
35 template.replace(template.indexOf("[NAME]"), template.indexOf("[NAME]") + 6, "Priya");
36 template.replace(template.indexOf("[ORDER_ID]"),template.indexOf("[ORDER_ID]")+ 10,"ORD-2024-001");
37 template.replace(template.indexOf("[AMOUNT]"), template.indexOf("[AMOUNT]") + 8, "1299.50");
38 template.replace(template.indexOf("[STATUS]"), template.indexOf("[STATUS]") + 8, "PLACED");
39
40 System.out.println("Filled template: " + template);
41 }
42}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.
1// File: ReverseDemo.java
2
3public class ReverseDemo {
4
5 public static void main(String[] args) {
6
7 // Basic reverse
8 StringBuilder sb = new StringBuilder("Hello World");
9 System.out.println("Original : " + sb);
10 sb.reverse();
11 System.out.println("Reversed : " + sb);
12
13 System.out.println();
14
15 // Check palindrome using reverse()
16 String[] words = {"racecar", "madam", "hello", "level", "Java"};
17 System.out.println("Palindrome check:");
18 for (String word : words) {
19 String reversed = new StringBuilder(word).reverse().toString();
20 boolean isPalindrome = word.equalsIgnoreCase(reversed);
21 System.out.printf(" %-10s → %s%n", word, isPalindrome ? "palindrome" : "not palindrome");
22 }
23
24 System.out.println();
25
26 // Reverse words in a sentence (not characters)
27 String sentence = "Java is awesome";
28 String[] wordsArr = sentence.split(" ");
29 StringBuilder reversed = new StringBuilder();
30 for (int i = wordsArr.length - 1; i >= 0; i--) {
31 reversed.append(wordsArr[i]);
32 if (i > 0) reversed.append(" ");
33 }
34 System.out.println("Original sentence : " + sentence);
35 System.out.println("Reversed words : " + reversed);
36 }
37}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.
1// File: SearchModifyDemo.java
2
3public class SearchModifyDemo {
4
5 public static void main(String[] args) {
6
7 StringBuilder sb = new StringBuilder("Java is great. Java is powerful.");
8
9 // indexOf() — first occurrence
10 System.out.println("indexOf('Java') : " + sb.indexOf("Java")); // 0
11 System.out.println("indexOf('Java', 5) : " + sb.indexOf("Java", 5)); // 15
12 System.out.println("lastIndexOf('Java') : " + sb.lastIndexOf("Java")); // 15
13 System.out.println("indexOf('Python') : " + sb.indexOf("Python")); // -1
14
15 System.out.println();
16
17 // charAt() — read a character at position
18 System.out.println("charAt(0) : " + sb.charAt(0)); // J
19 System.out.println("charAt(5) : " + sb.charAt(5)); // s
20
21 // setCharAt() — modify a single character
22 System.out.println("\nBefore setCharAt: " + sb);
23 sb.setCharAt(0, 'j'); // change 'J' to 'j'
24 System.out.println("After setCharAt(0,'j'): " + sb);
25
26 System.out.println();
27
28 // Practical use — capitalise first letter of each sentence
29 StringBuilder text = new StringBuilder("hello world. this is java. it is fun.");
30 text.setCharAt(0, Character.toUpperCase(text.charAt(0)));
31 int dotPos = text.indexOf(". ");
32 while (dotPos != -1 && dotPos + 2 < text.length()) {
33 text.setCharAt(dotPos + 2, Character.toUpperCase(text.charAt(dotPos + 2)));
34 dotPos = text.indexOf(". ", dotPos + 1);
35 }
36 System.out.println("Capitalised: " + text);
37 }
38}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.
1// File: SubstringBuilderDemo.java
2
3public class SubstringBuilderDemo {
4
5 public static void main(String[] args) {
6
7 StringBuilder sb = new StringBuilder("Order: ORD-2024-001 | Customer: Priya | Amount: Rs.1299");
8
9 // substring(start) — from position to end
10 System.out.println("From index 7 : " + sb.substring(7));
11
12 // substring(start, end) — from start inclusive to end exclusive
13 System.out.println("Order ID : " + sb.substring(7, 19)); // ORD-2024-001
14
15 // Find and extract dynamically
16 int custStart = sb.indexOf("Customer: ") + 10;
17 int custEnd = sb.indexOf(" |", custStart);
18 if (custEnd == -1) custEnd = sb.length();
19 System.out.println("Customer name : " + sb.substring(custStart, custEnd));
20
21 System.out.println();
22
23 // length() — current number of characters
24 System.out.println("Buffer length: " + sb.length());
25
26 // capacity() — current internal buffer size
27 System.out.println("Buffer capacity: " + sb.capacity());
28 }
29}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
| Aspect | String | StringBuilder | StringBuffer |
|---|---|---|---|
| Mutable | No — immutable | Yes — mutable in place | Yes — mutable in place |
| Thread-safe | Yes — immutable is safe | No — not synchronised | Yes — all methods synchronised |
| Performance in loops | Poor — creates new object per concat | Fast — single buffer throughout | Slower than StringBuilder — sync overhead |
| Memory | Many short-lived objects | One buffer, one final String | One buffer, one final String |
| When to use | Fixed values, constants, comparisons | Building strings in loops — single thread | Building strings across multiple threads |
+ operator | Yes | No — use append() | No — use append() |
| Return type of methods | New String | Same StringBuilder (chainable) | Same StringBuffer (chainable) |
| Introduced | Java 1.0 | Java 1.5 | Java 1.0 |
| Package | java.lang | java.lang | java.lang |
| Main methods | trim(), split(), replace() (returns new) | append(), insert(), delete(), reverse() | Same as StringBuilder |
Performance Comparison — + vs StringBuilder
1// File: PerformanceDemo.java
2
3public class PerformanceDemo {
4
5 public static void main(String[] args) {
6
7 int iterations = 100_000;
8
9 // Method 1 — String concatenation with +
10 long start1 = System.currentTimeMillis();
11 String result1 = "";
12 for (int i = 0; i < iterations; i++) {
13 result1 = result1 + i + ",";
14 }
15 long time1 = System.currentTimeMillis() - start1;
16 System.out.println("String + : " + time1 + " ms (length: " + result1.length() + ")");
17
18 // Method 2 — StringBuilder
19 long start2 = System.currentTimeMillis();
20 StringBuilder sb = new StringBuilder();
21 for (int i = 0; i < iterations; i++) {
22 sb.append(i).append(",");
23 }
24 String result2 = sb.toString();
25 long time2 = System.currentTimeMillis() - start2;
26 System.out.println("StringBuilder : " + time2 + " ms (length: " + result2.length() + ")");
27
28 System.out.println();
29 if (time1 > 0 && time2 > 0) {
30 System.out.println("StringBuilder was ~" + (time1 / Math.max(time2, 1)) + "x faster");
31 } else {
32 System.out.println("StringBuilder was significantly faster");
33 }
34 }
35}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.
Real-World Example — HTML Email Builder for an Order System
The Business Problem
A notification service at a company like Flipkart or Meesho generates HTML email bodies for order confirmations. The email contains a personalised greeting, an order summary table with multiple rows, and a footer. Each row of the table requires multiple string fragments joined together. Building this with + in a loop would create hundreds of temporary String objects. StringBuilder builds the entire email in one buffer.
1// File: OrderItem.java
2
3public class OrderItem {
4 private final String productName;
5 private final int quantity;
6 private final double unitPrice;
7
8 public OrderItem(String productName, int quantity, double unitPrice) {
9 this.productName = productName;
10 this.quantity = quantity;
11 this.unitPrice = unitPrice;
12 }
13
14 public String getProductName() { return productName; }
15 public int getQuantity() { return quantity; }
16 public double getUnitPrice() { return unitPrice; }
17 public double getLineTotal() { return quantity * unitPrice; }
18}1// File: EmailBuilder.java
2
3import java.util.List;
4
5public class EmailBuilder {
6
7 // Builds a complete HTML order confirmation email
8 public static String buildOrderConfirmationEmail(
9 String customerName,
10 String orderId,
11 List<OrderItem> items,
12 String deliveryAddress,
13 String estimatedDelivery) {
14
15 // Estimate final size — avoids resizing
16 StringBuilder html = new StringBuilder(1024);
17
18 // Extract first name for personalisation
19 String firstName = customerName.contains(" ")
20 ? customerName.substring(0, customerName.indexOf(' '))
21 : customerName;
22
23 // HTML header
24 html.append("<!DOCTYPE html><html><body>")
25 .append("<div style='font-family:Arial,sans-serif;max-width:600px;margin:auto'>")
26 .append("<h2 style='color:#ff5722'>Order Confirmed!</h2>")
27 .append("<p>Hi <strong>").append(firstName).append("</strong>,</p>")
28 .append("<p>Thank you for your order. Here are the details:</p>");
29
30 // Order info box
31 html.append("<div style='background:#f5f5f5;padding:12px;border-radius:6px'>")
32 .append("<b>Order ID:</b> ").append(orderId).append("<br>")
33 .append("<b>Estimated Delivery:</b> ").append(estimatedDelivery).append("<br>")
34 .append("<b>Deliver To:</b> ").append(deliveryAddress)
35 .append("</div><br>");
36
37 // Items table header
38 html.append("<table width='100%' cellpadding='8' style='border-collapse:collapse'>")
39 .append("<tr style='background:#ff5722;color:white'>")
40 .append("<th align='left'>Product</th>")
41 .append("<th align='center'>Qty</th>")
42 .append("<th align='right'>Price</th>")
43 .append("<th align='right'>Total</th>")
44 .append("</tr>");
45
46 // Items table rows + calculate grand total
47 double grandTotal = 0;
48 for (int i = 0; i < items.size(); i++) {
49 OrderItem item = items.get(i);
50 String rowBg = (i % 2 == 0) ? "#ffffff" : "#fafafa";
51 grandTotal += item.getLineTotal();
52
53 html.append("<tr style='background:").append(rowBg).append("'>")
54 .append("<td>").append(item.getProductName()).append("</td>")
55 .append("<td align='center'>").append(item.getQuantity()).append("</td>")
56 .append("<td align='right'>Rs.").append(String.format("%.2f", item.getUnitPrice())).append("</td>")
57 .append("<td align='right'>Rs.").append(String.format("%.2f", item.getLineTotal())).append("</td>")
58 .append("</tr>");
59 }
60
61 // Total row
62 html.append("<tr style='font-weight:bold;border-top:2px solid #ff5722'>")
63 .append("<td colspan='3' align='right'>Grand Total:</td>")
64 .append("<td align='right'>Rs.").append(String.format("%.2f", grandTotal)).append("</td>")
65 .append("</tr>")
66 .append("</table>");
67
68 // Footer
69 html.append("<br><p style='color:#888;font-size:12px'>")
70 .append("This is an auto-generated email. Please do not reply.<br>")
71 .append("For support, contact <a href='mailto:support@flipkart.com'>support@flipkart.com</a>")
72 .append("</p>")
73 .append("</div></body></html>");
74
75 return html.toString();
76 }
77}1// File: EmailBuilderDemo.java
2
3import java.util.List;
4
5public class EmailBuilderDemo {
6
7 public static void main(String[] args) {
8
9 List<OrderItem> items = List.of(
10 new OrderItem("Wireless Headphones", 1, 2499.00),
11 new OrderItem("USB-C Hub", 2, 899.00),
12 new OrderItem("Laptop Stand", 1, 1299.00),
13 new OrderItem("Mechanical Keyboard", 1, 3999.00)
14 );
15
16 String email = EmailBuilder.buildOrderConfirmationEmail(
17 "Priya Sharma",
18 "ORD-2024-88291",
19 items,
20 "14, Koramangala 4th Block, Bengaluru - 560034",
21 "Jan 18, 2024 (2-3 business days)"
22 );
23
24 // Print stats about the built email
25 System.out.println("Email built successfully.");
26 System.out.println("Total characters: " + email.length());
27 System.out.println("Contains header : " + email.contains("Order Confirmed!"));
28 System.out.println("Contains orderId: " + email.contains("ORD-2024-88291"));
29 System.out.println("Contains total : " + email.contains("Rs.9596.00"));
30
31 System.out.println();
32
33 // Show a readable snippet
34 System.out.println("=== Email Snippet (first 300 chars) ===");
35 System.out.println(email.substring(0, Math.min(300, email.length())));
36 System.out.println("...");
37 }
38}Output:
Email built successfully.
Total characters: 1642
Contains header : true
Contains orderId: true
Contains total : true
=== Email Snippet (first 300 chars) ===
<!DOCTYPE html><html><body><div style='font-family:Arial,sans-serif;max-width:600px;margin:auto'><h2 style='color:#ff5722'>Order Confirmed!</h2><p>Hi <strong>Priya</strong>,</p><p>Thank you for your order. Here are the details:</p><div style='background:#f5f5f5;padding:12px;border-radius:6px'><b>Order ID:</b> ORD-2024-88291...
A 1,642-character HTML email built entirely inside one StringBuilder buffer. Four items in the table, each appended in a loop. If each loop iteration used +, Java would have created around 40 intermediate String objects. StringBuilder creates zero intermediate objects — only the final toString() call produces a String.
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
1StringBuilder sb = new StringBuilder();
2for (String word : words) {
3 sb.append(word).append(" ");
4 String partial = sb.toString(); // bad — creates String on every iteration
5 System.out.println(partial); // use sb.toString() only once at the end
6}
7
8// If you need to print inside the loop:
9for (String word : words) {
10 sb.append(word).append(" ");
11 System.out.println(sb); // println calls toString() — acceptable for debugging
12}Mistake 2 — Using StringBuilder When String Is Enough
1// Over-engineering — only two parts, no loop, no modification needed
2StringBuilder sb = new StringBuilder();
3sb.append("Hello, ");
4sb.append(name);
5String greeting = sb.toString();
6
7// Simpler — + is fine for a small fixed number of concatenations
8String 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()
1// String.replace() — replaces ALL occurrences of a substring
2String result = "Java Java Java".replace("Java", "Python"); // "Python Python Python"
3
4// StringBuilder.replace() — replaces a CHARACTER RANGE by indices
5StringBuilder sb = new StringBuilder("Java Java Java");
6sb.replace(5, 9, "Python"); // replaces chars at index 5-8 with "Python"
7System.out.println(sb); // "Java Python Java" — only the range, not all occurrencesString.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
1StringBuilder sb = new StringBuilder();
2for (String item : items) {
3 sb.append(item).append(", "); // always appends ", "
4}
5System.out.println(sb); // "Laptop, Mouse, Keyboard, " ← trailing ", "
6
7// Fix — delete the last two characters
8if (sb.length() >= 2) {
9 sb.delete(sb.length() - 2, sb.length());
10}
11
12// Alternative fix — check before appending separator
13for (int i = 0; i < items.length; i++) {
14 if (i > 0) sb.append(", ");
15 sb.append(items[i]);
16}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.