StringBuilder and StringBuffer in Java
When text is built piece by piece, a mutable buffer avoids the copying that repeated String concatenation forces.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
Why they exist
String is immutable, so every concatenation creates a new object and copies the characters. Building text in a loop therefore does work proportional to the square of the final length. StringBuilder and StringBuffer are mutable character buffers that append in place instead.
The problem, measured in objects
String result = "";
for (int i = 0; i < 5; i++) {
result += i; // creates "0", "01", "012", "0123", "01234"
}Five appends created five strings and copied every earlier character each time. At five iterations this is invisible; at fifty thousand it dominates the program.
StringBuilder
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 5; i++) {
builder.append(i);
}
String result = builder.toString(); // "01234"The buffer grows only when its capacity runs out, and appending writes into the existing array. One object is created, not five.
The main methods
StringBuilder sb = new StringBuilder("Java");
sb.append(" Notes"); // Java Notes
sb.insert(0, "Core "); // Core Java Notes
sb.replace(0, 4, "Basic"); // Basic Java Notes
sb.delete(0, 6); // Java Notes
sb.reverse(); // setoN avaJ
sb.setLength(4); // setoN avaJ trimmed to seto
System.out.println(sb.length());
System.out.println(sb.capacity()); // allocated size, at least the length
System.out.println(sb.indexOf("e"));Every mutating method returns the same builder, so calls chain naturally:
String line = new StringBuilder()
.append("Order ").append(1042)
.append(" total ").append(2499.50)
.toString();StringBuffer
StringBuffer has the same API but every method is synchronized, which makes an instance safe to share across threads. That synchronisation costs time on every call, and it is unnecessary for a buffer used inside a single method.
Comparison
| Aspect | String | StringBuilder | StringBuffer |
|---|---|---|---|
| Mutable | No | Yes | Yes |
| Thread safe | Yes, by immutability | No | Yes, synchronized |
| Speed for appends | Slowest | Fastest | Slower than builder |
| Introduced | Java 1.0 | Java 5 | Java 1.0 |
| Use when | Values are fixed | Building text locally | A buffer is genuinely shared |
The practical rule: useStringby default,StringBuilderwhen building text, andStringBufferalmost never. A shared mutable buffer is usually a design problem rather than a threading requirement.
Capacity
StringBuilder sb = new StringBuilder(1024); // pre-sized, avoids regrowthWhen the final size is roughly known, supplying it prevents repeated array growth and copying. This matters only in hot code, so measure before tuning.
equals is not overridden
StringBuilder a = new StringBuilder("x");
StringBuilder b = new StringBuilder("x");
System.out.println(a.equals(b)); // false - identity
System.out.println(a.toString().equals(b.toString())); // trueNeither builder overrides equals or hashCode, so they must never be used as map keys or set elements.
A practical example
public static String toCsvRow(List<String> fields) {
StringBuilder row = new StringBuilder();
for (int i = 0; i < fields.size(); i++) {
if (i > 0) {
row.append(',');
}
row.append(fields.get(i).replace(",", " "));
}
return row.toString();
}For this particular job String.join is simpler, and for streams Collectors.joining is simpler still. Reach for a builder when the assembly logic is more involved than a plain join.
Common mistakes
- Using
StringBufferout of habit and paying for synchronisation that is never needed. - Calling
toStringinside the loop instead of once at the end. - Comparing builders with
equals. - Using a builder for a single concatenation, which is slower and harder to read than
+. - Confusing
capacitywithlength.
Best practices
- Keep the builder local to the method that fills it.
- Pre-size it when the result length is predictable and the code is hot.
- Prefer
String.joinorCollectors.joiningfor simple separator work. - Convert to
Stringonce, at the end.
Practice
- Rewrite a loop that concatenates 10000 numbers using a builder, and explain the difference in objects created.
- Why does
new StringBuilder("abc").reverse()change the object rather than returning a new one? - What does
capacity()report fornew StringBuilder("Java"), and why is it larger than 4? - Write a method that builds a formatted address block from several optional fields, skipping the blank ones.
- Explain in one sentence when
StringBufferwould genuinely be the right choice.
Conclusion
Immutable strings are the right default, and a mutable builder is the right tool for assembling text. Use StringBuilder locally, use StringBuffer only when a buffer is truly shared between threads, and convert to a String once at the end.