Java StringBuilder and StringBuffer: Mutable String Building
String in Java is immutable: every concatenation with + creates a new object. StringBuilder provides a mutable sequence of characters that you can modify in-place, making it the correct choice for building strings in loops. StringBuffer is the thread-safe variant. Understanding when to use each, and knowing StringBuilder's full API, leads to more efficient and expressive string manipulation.
Contents
Why StringBuilder over String +
String+StringStringBuilderchar[]+StringBuilderStringBuilder
// String + in a loop ā creates N intermediate String objects
// Each + is: new StringBuilder(left).append(right).toString()
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // O(n²) ā very slow for large loops
}
// StringBuilder ā single mutable buffer, O(n) amortized
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String result2 = sb.toString(); // convert to immutable String at the end
// The compiler optimizes simple string concatenation:
// String s = "Hello" + " " + "World"; // compiler merges to one literal
// String s = a + " " + b; // compiled to StringBuilder chain
// But loop concatenation is NOT optimized by the compiler
// When + is fine (single expression, not in a loop):
String msg = "Hello, " + name + "! You have " + count + " messages."; // OK
append, insert, delete, replace
append()thisinsert(offset, str)delete(start, end)[start, end)deleteCharAt(index)replace(start, end, str)StringBuilder
StringBuilder sb = new StringBuilder("Hello");
// append ā add to end (supports all types, returns this for chaining)
sb.append(", ");
sb.append("World");
sb.append('!');
sb.append(42);
sb.append(true);
System.out.println(sb); // Hello, World!42true
// Method chaining
StringBuilder chain = new StringBuilder()
.append("Java")
.append(" is ")
.append("awesome");
System.out.println(chain); // Java is awesome
// insert ā insert at position
sb = new StringBuilder("Hello World");
sb.insert(5, " Beautiful"); // insert before index 5
System.out.println(sb); // Hello Beautiful World
sb.insert(0, ">>> "); // prepend
System.out.println(sb); // >>> Hello Beautiful World
// delete ā remove characters [start, end)
sb = new StringBuilder("Hello Beautiful World");
sb.delete(5, 15); // removes " Beautiful"
System.out.println(sb); // Hello World
sb.deleteCharAt(0); // remove single char at index
System.out.println(sb); // ello World
// replace ā replace characters [start, end) with new string
sb = new StringBuilder("Hello World");
sb.replace(6, 11, "Java");
System.out.println(sb); // Hello Java
// setCharAt ā overwrite a single character
sb.setCharAt(0, 'h');
System.out.println(sb); // hello Java
indexOf, reverse, and charAt
indexOf(str)-1reverse()charAt(index)setCharAt(index, ch)length()capacity()
StringBuilder sb = new StringBuilder("Hello World Hello");
// indexOf / lastIndexOf ā find substring
System.out.println(sb.indexOf("Hello")); // 0
System.out.println(sb.lastIndexOf("Hello")); // 12
System.out.println(sb.indexOf("Hello", 1)); // 12 (start searching from index 1)
System.out.println(sb.indexOf("xyz")); // -1 (not found)
// charAt / length
System.out.println(sb.charAt(6)); // 'W'
System.out.println(sb.length()); // 17
// substring ā returns a new String (does not modify sb)
System.out.println(sb.substring(6)); // "World Hello"
System.out.println(sb.substring(6, 11)); // "World"
// reverse ā reverses in-place
StringBuilder rev = new StringBuilder("abcde");
rev.reverse();
System.out.println(rev); // edcba
// Palindrome check using reverse
static boolean isPalindrome(String s) {
return s.equals(new StringBuilder(s).reverse().toString());
}
System.out.println(isPalindrome("racecar")); // true
System.out.println(isPalindrome("hello")); // false
// toString ā get the immutable String
String result = sb.toString();
Capacity Management
A StringBuildernew StringBuilder(estimatedSize)trimToSize()StringBuilder
// Initial capacity is 16 chars; auto-grows when needed
StringBuilder sb = new StringBuilder(); // capacity: 16
StringBuilder sb2 = new StringBuilder(1000); // pre-allocated for 1000 chars
StringBuilder sb3 = new StringBuilder("Hi"); // capacity: 16 + "Hi".length() = 18
System.out.println(sb.capacity()); // 16
sb.append("Hello");
System.out.println(sb.capacity()); // 16 (still, 5 < 16)
sb.append("Hello World How Are You Doing"); // grows beyond 16
System.out.println(sb.capacity()); // auto-grown (typically (old+1)*2)
// ensureCapacity ā guarantee at least N chars before growing
sb.ensureCapacity(500); // avoid multiple re-allocations if you know the size
// trimToSize ā release unused capacity
sb = new StringBuilder(1000);
sb.append("hi");
sb.trimToSize(); // capacity trimmed to 2
// Pre-sizing for performance ā if you know the final size
int n = 100_000;
StringBuilder preAllocated = new StringBuilder(n * 5); // avoid all reallocations
for (int i = 0; i < n; i++) {
preAllocated.append(i).append(',');
}
StringBuilderStringBuffer: Thread-safe Variant
StringBufferStringBuildersynchronizedStringBuilderStringBuilderStringBuffer
// StringBuffer has the exact same API as StringBuilder
// but every method is synchronized ā thread-safe but slower
StringBuffer tsf = new StringBuffer();
tsf.append("Hello").append(" World");
System.out.println(tsf.toString()); // Hello World
// When to use StringBuffer vs StringBuilder:
// - StringBuilder: use in single-threaded code (most cases) ā faster
// - StringBuffer: use when the mutable string is shared across threads
// (rare ā usually better to avoid shared mutable state)
// Modern alternative for thread safety: use local StringBuilder,
// then publish the final immutable String
// String is already thread-safe (immutable)
// Example: building a log line across thread-shared code
// BAD: shared StringBuffer (all threads serialize on it)
// GOOD: each thread uses its own StringBuilder, shares only the final String
// CharSequence ā common interface of String, StringBuilder, StringBuffer
CharSequence cs = new StringBuilder("hello");
CharSequence cs2 = "hello"; // String also implements CharSequence
System.out.println(cs.length()); // 5
StringBuilderStringBufferStringBufferString