☕ Java
StringBuilder
StringBuilder is a mutable sequence of characters introduced in Java 5 as the preferred alternative to StringBuffer for single-threaded use. It provides an identical API to StringBuffer but without synchronisation, making it faster in all scenarios where thread safety is not required. In modern Java, StringBuilder is the standard tool for building strings dynamically — assembling CSV rows, constructing SQL queries, generating formatted output, and any situation where a string is built up from parts.
StringBuilder vs StringBuffer — When to Use Which
StringBuilder and StringBuffer have identical APIs — every method available on StringBuffer is available on StringBuilder with the same name, parameters, and semantics. The sole difference is that StringBuffer synchronises all its public methods (making it thread-safe) while StringBuilder does not synchronise any (making it faster but not thread-safe).
Thread safety in StringBuffer means that when multiple threads call methods on the same StringBuffer instance simultaneously, the operations are serialised — only one thread proceeds at a time. This prevents data corruption but adds locking overhead to every single method call, even when the buffer is only used by one thread.
The vast majority of string building is single-threaded. A method that builds a string to return, a loop that constructs a CSV row, a toString() method that assembles a description — none of these involve multiple threads accessing the same StringBuilder. For all such cases, StringBuilder is the correct choice: it provides the same mutable string building with no synchronisation overhead.
StringBuffer remains relevant in two specific scenarios: when a single StringBuffer object is genuinely shared and mutated by multiple threads concurrently (rare), and when working with legacy code that already uses StringBuffer. For any new code in a single-threaded context, which covers the overwhelming majority of string building tasks, use StringBuilder.
The Java compiler itself uses StringBuilder internally when it generates code for string concatenation with the + operator (in JDK 8 and earlier). Starting with JDK 9, the compiler uses invokedynamic with StringConcatFactory, which is even more optimised, but the principle is the same — StringBuilder-like buffering rather than repeated String allocation.
Java
// ── StringBuilder — identical API to StringBuffer, no synchronisation: ─
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" World");
System.out.println(sb.toString()); // Hello World
// ── Performance difference — StringBuilder wins in single-threaded code: ─
// StringBuffer: every method call acquires and releases a lock
// StringBuilder: no locking overhead
long start;
// StringBuffer (synchronised):
start = System.nanoTime();
StringBuffer sbuf = new StringBuffer();
for (int i = 0; i < 100_000; i++) sbuf.append('x');
System.out.println("StringBuffer: " + (System.nanoTime()-start) + "ns");
// StringBuilder (unsynchronised):
start = System.nanoTime();
StringBuilder sbldr = new StringBuilder();
for (int i = 0; i < 100_000; i++) sbldr.append('x');
System.out.println("StringBuilder: " + (System.nanoTime()-start) + "ns");
// StringBuilder is typically 10-20% faster
// ── When StringBuffer is correct — shared mutable state: ─────────────
// A shared log buffer in a multi-threaded application:
public class ThreadSafeLogger {
private final StringBuffer log = new StringBuffer(); // StringBuffer!
public void record(String message) {
log.append(Thread.currentThread().getName())
.append(": ")
.append(message)
.append('
');
}
public String getLog() { return log.toString(); }
}
// ── When StringBuilder is correct — local, single-threaded: ──────────
public String formatOrder(Order order) {
StringBuilder sb = new StringBuilder(); // StringBuilder!
sb.append("Order #").append(order.getId()).append('
');
for (OrderItem item : order.getItems()) {
sb.append(" ").append(item.getName())
.append(" x").append(item.getQty())
.append(" = £").append(item.getTotal()).append('
');
}
sb.append("Total: £").append(order.getTotal());
return sb.toString();
// sb goes out of scope — only one thread ever touched it
}StringBuilder Methods and Method Chaining
StringBuilder has the same comprehensive API as StringBuffer — append(), insert(), delete(), replace(), reverse(), charAt(), setCharAt(), indexOf(), substring(), length(), capacity(), and toString(). All mutating methods return the StringBuilder itself, enabling fluent method chaining where multiple operations are composed into a single expression.
Method chaining with StringBuilder is idiomatic Java. Rather than writing separate statements for each append, you chain them together to express a sequence of additions as a single logical operation. The result is more concise and reads naturally from left to right. This style is used extensively in toString() implementations, in formatting methods, and in any method that assembles a structured string.
The deleteCharAt(length() - 1) pattern is especially common for removing a trailing delimiter character (comma, space, pipe) after a loop. A loop that appends "item, " for each item ends with a trailing comma and space — deleteCharAt removes the last character. Alternatively, you can check whether it is the last iteration and conditionally append the delimiter only between items.
StringBuilder also supports the CharSequence interface, which means it can be used anywhere a CharSequence is expected — including the append() method of another StringBuilder, Pattern.matcher(), and other APIs that accept general sequences of characters. This interoperability makes StringBuilder a versatile building block.
Java
// ── Method chaining — fluent building: ────────────────────────────────
String result = new StringBuilder()
.append("{
")
.append(" "name": "Alice",
")
.append(" "age": 30,
")
.append(" "active": true
")
.append("}")
.toString();
System.out.println(result);
// ── Building CSV row: ──────────────────────────────────────────────────
String[] values = {"Alice", "30", "Engineer", "London"};
StringBuilder csv = new StringBuilder();
for (int i = 0; i < values.length; i++) {
csv.append(values[i]);
if (i < values.length - 1) csv.append(',');
}
System.out.println(csv); // Alice,30,Engineer,London
// ── Remove trailing delimiter pattern: ───────────────────────────────
StringBuilder sb = new StringBuilder();
for (String v : values) {
sb.append(v).append(", ");
}
if (sb.length() >= 2) {
sb.delete(sb.length() - 2, sb.length()); // remove last ", "
}
System.out.println(sb); // Alice, 30, Engineer, London
// ── insert() at position: ─────────────────────────────────────────────
StringBuilder path = new StringBuilder("/api/users");
path.insert(0, "https://example.com");
System.out.println(path); // https://example.com/api/users
// ── replace() a range: ────────────────────────────────────────────────
StringBuilder query = new StringBuilder("SELECT * FROM users WHERE id = ?");
query.replace(0, 8, "SELECT id, name");
System.out.println(query); // SELECT id, name FROM users WHERE id = ?
// ── reverse(): ────────────────────────────────────────────────────────
System.out.println(new StringBuilder("racecar").reverse().toString()); // racecar
System.out.println(new StringBuilder("hello").reverse().toString()); // olleh
// ── Palindrome check using reverse: ──────────────────────────────────
public static boolean isPalindrome(String s) {
String cleaned = s.toLowerCase().replaceAll("[^a-z0-9]", "");
return cleaned.equals(new StringBuilder(cleaned).reverse().toString());
}
System.out.println(isPalindrome("racecar")); // true
System.out.println(isPalindrome("A man a plan a canal Panama")); // true
System.out.println(isPalindrome("hello")); // falsePractical Patterns with StringBuilder
Several recurring patterns in Java programming are best implemented with StringBuilder. Building complex strings from structured data, implementing custom toString() methods, generating formatted reports, and assembling dynamic SQL queries all benefit from StringBuilder's efficient in-place mutation and fluent chaining API.
The toString() implementation pattern uses StringBuilder to assemble a readable representation of an object's state. The convention is to include the class name and key fields in a format like ClassName{field1=value1, field2=value2}. StringBuilder makes it easy to assemble this format without creating intermediate String objects.
Building structured text output — tables, formatted reports, indented trees — is another natural StringBuilder application. Methods that generate multi-line output can use append('
') between lines and append(" ") for indentation, building the entire output string before returning it rather than printing line by line. This is useful when the output needs to be stored, logged, or tested rather than printed immediately.
The StringBuilder-per-item, then join pattern is the most efficient way to build a delimited list. Appending each item followed by a delimiter and removing the last delimiter is a clean O(n) operation. Java 8 also introduced String.join() and Collectors.joining() in streams, which use StringBuilder internally and are even more concise for collection-to-string operations.
Java
// ── Custom toString() with StringBuilder: ────────────────────────────
public class Employee {
private int id;
private String name;
private String department;
private double salary;
@Override
public String toString() {
return new StringBuilder("Employee{")
.append("id=").append(id)
.append(", name='").append(name).append(''')
.append(", department='").append(department).append(''')
.append(", salary=").append(salary)
.append('}')
.toString();
}
}
// ── Building an indented tree structure: ─────────────────────────────
public String formatTree(TreeNode root, int depth) {
StringBuilder sb = new StringBuilder();
sb.append(" ".repeat(depth)).append(root.getValue()).append('
');
for (TreeNode child : root.getChildren()) {
sb.append(formatTree(child, depth + 1));
}
return sb.toString();
}
// ── Generating a formatted table: ────────────────────────────────────
public static String formatTable(String[] headers, String[][] rows) {
StringBuilder sb = new StringBuilder();
// Calculate column widths:
int[] widths = new int[headers.length];
for (int c = 0; c < headers.length; c++) {
widths[c] = headers[c].length();
for (String[] row : rows) {
widths[c] = Math.max(widths[c], row[c].length());
}
}
// Build header:
for (int c = 0; c < headers.length; c++) {
sb.append(String.format("%-" + (widths[c]+1) + "s", headers[c]));
}
sb.append('
');
// Separator:
for (int w : widths) {
sb.append("-".repeat(w + 1));
}
sb.append('
');
// Data rows:
for (String[] row : rows) {
for (int c = 0; c < row.length; c++) {
sb.append(String.format("%-" + (widths[c]+1) + "s", row[c]));
}
sb.append('
');
}
return sb.toString();
}
String[] headers = {"Name", "Department", "Salary"};
String[][] data = {
{"Alice", "Engineering", "75000"},
{"Bob", "Marketing", "65000"},
{"Carol", "Engineering", "80000"}
};
System.out.print(formatTable(headers, data));
// ── String.join() — often replaces manual StringBuilder joins: ────────
List<String> parts = List.of("apple", "banana", "cherry");
System.out.println(String.join(", ", parts)); // apple, banana, cherry
// ── Collectors.joining() for streams: ────────────────────────────────
String names = Stream.of("Alice", "Bob", "Charlie")
.map(String::toLowerCase)
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(names); // [alice, bob, charlie]Related Topics in Strings
String Class
String is one of the most fundamental classes in Java — used in virtually every program, yet deeply misunderstood by many developers. A String represents an immutable sequence of Unicode characters. It is not a primitive type but a full class in java.lang, automatically imported into every Java file. Understanding String means understanding how it is stored in memory, why it is immutable, how the string pool works, what the difference between == and equals() means for strings, and how to use the class efficiently. This entry covers String's nature as a class, its internal representation, the critical distinction between reference equality and value equality, String's place in the type hierarchy, and the design decisions that make String behave the way it does.
String Pool
The string pool (also called the string intern pool or string constant pool) is a special memory region maintained by the JVM that stores a single copy of each unique string value. When two string literals have the same content, they refer to the same object in the pool rather than two separate objects. The pool is a flyweight pattern applied at the language level — it dramatically reduces memory consumption in applications that use many repeated string values, which is nearly every application. This entry covers how the pool works, where it lives in JVM memory, how to interact with it programmatically, the intern() method, performance implications, and when to use or avoid pool entries.
Immutable String
String immutability is the most important design decision in Java's String class. Once a String object is created, its character sequence can never change. No method on String modifies the string; every method that appears to modify returns a new String object containing the result. This design decision drives thread safety, enables the string pool, makes strings safe hash map keys, and simplifies reasoning about string values. Understanding why String is immutable, how immutability is enforced, and what the consequences of immutability are clarifies the behaviour of virtually every piece of Java code that handles strings.
Mutable String
Java provides two mutable string classes for scenarios where String's immutability would be inefficient: StringBuilder and StringBuffer. Both maintain an internal character buffer that can be modified in place — characters can be appended, inserted, deleted, and replaced without creating new objects. StringBuilder is the modern choice for single-threaded use; StringBuffer is the legacy thread-safe version with synchronised methods. This entry covers the internal buffer mechanics, the full API of both classes, performance characteristics, when to use each, thread safety implications, and the complete patterns for efficient string construction.