By WebNest Studio Engineering Team
Java - Core Tutorial
Java StringBuilder
StringBuilder, introduced in Java 5, offers the exact same mutable, buffer-based API as StringBuffer — append, insert, delete, reverse, replace — but without any synchronization. Because most Java code that builds strings runs on a single thread (inside one method, one loop, or one request handler), the synchronization that StringBuffer performs is usually unnecessary overhead, and StringBuilder was added specifically to avoid paying for it.
As a result, StringBuilder is the recommended default choice for mutable string construction in modern Java code, and it is exactly what the compiler uses internally to optimize simple String concatenation with "+" inside a single expression.
Why it matters
StringBuilder is what you should use whenever you assemble text in a loop: building a CSV line, a log message, an HTML fragment or a SQL clause. Replacing string concatenation in a loop with a StringBuilder is one of the simplest performance fixes in Java, and one that code reviewers look for.
Watch: How StringBuilder Fixes Java Memory
A quick video on how one growing buffer replaces the throwaway Strings that += creates in a loop.
How StringBuilder Fixes the Memory Problem
Building text with += in a loop looks harmless, but because Strings are immutable each pass creates a brand-new String and copies every character built so far into it. The previous String becomes garbage immediately. For a loop of n steps that is n throwaway objects and a growing amount of copying each time.
A StringBuilder keeps one resizable character array instead. It starts with room for 16 characters, and append() writes straight into the free space. Only when the array is full does it allocate a bigger one — roughly double the size plus two — and copy the contents across once. So thousands of appends cost a handful of allocations rather than thousands, and a single toString() at the end produces the final String.
Same API, No Synchronization
StringBuilder and StringBuffer implement the same CharSequence-based operations and share method signatures almost identically, so switching between them (when thread-safety requirements change) is usually just a matter of changing the type name. The difference is entirely about the synchronized keyword being present on StringBuffer's methods and absent on StringBuilder's.
append(...)— adds content to the end; returns the same StringBuilder for chaining.insert(int offset, ...)— inserts content at a given index.delete(int start, int end),deleteCharAt(int index)— remove content.reverse()— reverses the sequence in place.capacity()— reports the current size of the internal buffer, which grows automatically as needed.
Why the Compiler Prefers It
When you write code like String result = "Total: " + count + " items";, the compiler typically rewrites this internally using a StringBuilder (or similar mechanism) rather than performing several separate String concatenations. This is why simple, single-line concatenation is not actually as wasteful as it might first appear — but building a String across many iterations of a loop with "+" is still inefficient, because a new StringBuilder (or new String) is effectively created on every loop iteration unless you manage one StringBuilder explicitly yourself.
When to Still Use StringBuffer Instead
StringBuilder should not be shared across multiple threads without external synchronization, because concurrent modifications can corrupt its internal state or produce inconsistent results. If multiple threads genuinely need to mutate the same buffer concurrently, StringBuffer (or a higher-level concurrency-safe design) remains the correct choice.
Examples
Efficiently building a String inside a loop with StringBuilder
public class StringBuilderDemo {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 5; i++) {
sb.append(i);
if (i < 5) {
sb.append(", ");
}
}
System.out.println("Numbers: " + sb.toString());
System.out.println("Length: " + sb.length());
sb.insert(0, "[").append("]");
System.out.println(sb);
}
}
Numbers: 1, 2, 3, 4, 5
Length: 13
[1, 2, 3, 4, 5]
One buffer that grows: capacity starts at 16 and expands only when it is full
public class BuilderCapacityDemo {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
System.out.println(sb.capacity()); // room for 16 characters
sb.append("0123456789ABCDEF"); // exactly 16 characters
System.out.println(sb.capacity()); // still 16, no new array needed
sb.append("G"); // the 17th character does not fit
System.out.println(sb.capacity()); // grown to (16 * 2) + 2
System.out.println(sb.length()); // characters actually stored
}
}
16
16
34
17
Common Mistakes
- Concatenating Strings with "+" inside a large loop instead of using a single StringBuilder — this creates many intermediate objects and hurts performance noticeably as the loop grows.
- Sharing one StringBuilder across multiple threads without external synchronization, leading to corrupted or unpredictable output.
- Forgetting to call toString() when a method or API expects a String rather than a StringBuilder object.
Exercise
Join the elements of an array into one line separated by a comma and a space, with no separator at the end.
Expected output: red, green, blue
public class JoinColours {
public static void main(String[] args) {
String[] colours = {"red", "green", "blue"};
StringBuilder line = new StringBuilder();
// TODO: append each colour, adding ", " between them but not after the last
System.out.println(line);
}
}
Show hints
- Add the separator before every element except the first, instead of trying to remove it at the end.
- An indexed loop lets you test
i > 0.
Show solution
public class JoinColours {
public static void main(String[] args) {
String[] colours = {"red", "green", "blue"};
StringBuilder line = new StringBuilder();
for (int i = 0; i < colours.length; i++) {
if (i > 0) {
line.append(", ");
}
line.append(colours[i]);
}
System.out.println(line); // red, green, blue
}
}
Quick quiz
1. Is StringBuilder safe to share between threads that modify it?
- A. No, its methods are not synchronized
- B. Yes, always
- C. Only when it is final
- D. Only for append()
Show answer
A. StringBuilder has no synchronization. Use it within a single thread, or use StringBuffer or external locking when sharing.
2. Why is result += piece inside a long loop slow?
- A. The + operator is not allowed in loops
- B. Each iteration creates a new String and copies all the previous characters
- C. It causes a memory leak
- D. It blocks other threads
Show answer
B. Strings are immutable, so every += builds a new String containing everything so far. The total copying grows with the square of the length.
3. Why can you write sb.append("a").append("b")?
- A. append() returns a new String
- B. append() returns void, and Java chains automatically
- C. append() returns the same StringBuilder
- D. It only works for two calls
Show answer
C. append() returns the builder it was called on, which allows calls to be chained.
Interview questions
What does the compiler do with String concatenation using +?
Show answer
For a single expression such as a + b + c, the compiler already produces efficient code: older compilers generated a StringBuilder, and since Java 9 it uses a runtime-optimised concatenation mechanism. The problem is a loop: each iteration is a separate expression, so a new String is created every time. That is the case where you should use an explicit StringBuilder.
How do you reuse a StringBuilder for the next piece of text?
Show answer
Call sb.setLength(0). It resets the length to zero while keeping the internal array, so the builder can be filled again without allocating new memory. This is useful when the same builder is used repeatedly inside a loop.
Key Points to Remember
- StringBuilder has the same mutable API as StringBuffer but is not synchronized, so it is faster in single-threaded code.
- It is the default, recommended choice for building strings dynamically in modern Java.
- It is not thread-safe — use StringBuffer or external synchronization for concurrent mutation.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering