Java's StringBuilder sits in the java.lang package as the go-to class for assembling strings when you need to modify them in place. It offers a mutable alternative to immutable Strings, which means you can change, append, insert, and delete characters without creating new objects each time. If you have ever written a loop that concatenates strings, you have already felt the pain that StringBuilder solves.
It deserves a closer look. Below is the short version, then the constructors and methods that actually matter, and finally a comparison that cuts through the noise.
What StringBuilder actually is
Java Strings are immutable. Concatenate three of them and the runtime spins up new objects each time. That is fine for a greeting card. It is wasteful when you are looping through a list of contacts to build a CSV export or assembling a chat transcript.
StringBuilder fixes that by keeping an internal char array that grows on demand. You mutate it in place, then call toString() when you need an immutable copy. The trade-off is thread safety: only one thread at a time should touch a given builder. In a typical single-threaded method, that is exactly what you have.
The four constructors and when to pick each one
| Constructor | What it does | Best for |
|---|---|---|
| StringBuilder() | Empty builder with 16-character capacity | Short strings where size is unknown |
| StringBuilder(int capacity) | Empty builder with custom capacity | When you can guess final length |
| StringBuilder(CharSequence seq) | Copies from any CharSequence | Wrapping existing sequences |
| StringBuilder(String str) | Wraps an existing String | Convenience when starting with String |
The custom-capacity constructor is the one most newcomers skip. If you can estimate the final length, pre-sizing saves array reallocations.
The methods that earn their keep
You will use a small handful repeatedly.
- Append(String s): the daily workhorse, with overloads for primitives and chars.
- Insert(int offset, String s): drops text at a specific position.
- Replace(int start, int end, String s): swaps a slice.
- Delete(int start, int end): trims a slice.
- Reverse(): flips the sequence, handy for palindrome checks.
- Capacity() and ensureCapacity(int min): inspect and pre-tune the backing array.
- CharAt, substring, indexOf, length: familiar query helpers.
A practical rule: reach for append in loops. Reach for replace when you know exact indices. Avoid chaining dozens of inserts in a tight loop.
StringBuilder vs StringBuffer: the real difference
| Feature | StringBuilder | StringBuffer |
|---|---|---|
| Synchronization | None | Every mutating method locked |
| Speed | Faster | Slower |
| Thread safety | None | Full |
| Best use | Single-threaded code | Shared buffers across threads |
StringBuffer locks on every append, insert, and replace, which keeps it safe across threads but costs throughput. For single-thread contexts, StringBuilder wins. Reach for StringBuffer only when multiple threads genuinely share one buffer.
Worked example: building a file summary
Say you need to generate one-line summaries: filename, size in KB, and status. Use a builder instead of concatenation.
“`java
StringBuilder strBuilder = new StringBuilder("ExampleFile.txt");
StrBuilder.append(" – 2048 KB");
StrBuilder.append(" – Complete");
System.out.println(strBuilder.toString());
“`
Inside loops, reuse the same builder by clearing its content, avoiding repeated allocations. Pre-size capacity when you know the approximate final length.
Our analysis
For most code, StringBuilder is the right default. It is faster than StringBuffer when threading is not a concern, and dramatically cheaper than repeated String concatenation inside loops.
The win is small per call, but it compounds in tight loops, log formatters, and data serializers. Every object you avoid creating is one less for the garbage collector to collect.
The trade-off is correctness in concurrent code. Sharing one builder across threads invites race conditions. Pass immutable Strings between threads instead, or use StringBuffer where shared mutation is genuinely required.
What to watch: newer String APIs like String.join and text blocks cover specific use cases, but for pure in-place assembly, StringBuilder remains the workhorse.
Key takeaways
- Default to StringBuilder for single-threaded string assembly.
- Pre-size capacity when you can estimate final length to avoid resizing.
- Reuse builders in hot paths by resetting their content.
- Switch to StringBuffer only when multiple threads must share the same buffer.
Frequently asked questions
When should I use StringBuilder instead of String?
Use StringBuilder whenever you are concatenating strings inside a loop or assembling output across multiple statements. Plain String concatenation creates a new object each time, which adds garbage collection pressure.
Is StringBuilder thread-safe like StringBuffer?
No. StringBuilder is not synchronized. It is meant for single-threaded use.
What is the default capacity of a new StringBuilder?
An empty builder created with the no-arg constructor starts with a 16-character capacity.
Can I reuse a StringBuilder instead of creating a new one?
Yes. You can reset the builder's content while keeping its backing array, which avoids reallocations in tight loops.
Does StringBuilder compile the same way in Kotlin?
Yes. Kotlin compiles string templates and builder calls down to java.lang.StringBuilder on the JVM, so the performance characteristics are identical.