Search⌘ K
AI Features

StringBuilder and StringBuffer

Explore how to use StringBuilder and StringBuffer in Java for efficient text processing. Understand the performance cost of immutable Strings and learn to manipulate text dynamically with methods like append, insert, delete, and reverse. Discover when to choose StringBuilder for fast single-threaded operations or StringBuffer for thread-safe scenarios. This lesson helps you write high-performance, maintainable Java code for dynamic string handling.

We discovered that Java String objects are immutable once created, they cannot be changed. While this ensures safety and security, it incurs a hidden performance cost when we frequently modify text. Every time we concatenate strings in a loop or modify a sentence, Java quietly creates entirely new objects in memory, discarding the old ones.

To solve this, Java provides powerful tools designed specifically for heavy-duty text manipulation. In this lesson, we will learn how to use StringBuilder and StringBuffer to write efficient, high-performance code that handles dynamic text with ease.

The cost of immutability

When we modify a String using the + operator, Java does not actually change the original object. Instead, it copies the existing characters, appends the new ones, and allocates memory for a brand-new object.

If we do this once or twice, the cost is negligible. However, inside a loop, this behavior can severely impact performance. Consider a scenario where we want to build a long sentence word by word.

If we use string concatenation, we flood memory with intermediate objects that are discarded immediately. This puts unnecessary pressure on the Garbage Collector, slowing down our application.

Here is how StringBuilder solves this problem by using a single, resizable container:

Java 25
public class StringPerformance {
public static void main(String[] args) {
// Inefficient: Creates a new String object in every iteration
String slowText = "";
for (int i = 0; i < 5; i++) {
slowText += i + " ";
}
System.out.println("String Result: " + slowText);
// Efficient: Modifies the same object internally
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 5; i++) {
builder.append(i).append(" ");
}
System.out.println("Builder Result: " + builder.toString());
}
}
  • Line 4: We initialize an empty string.

  • Line 6: Inside the loop, slowText += causes the creation of a new string object for every iteration (e.g., “0 ”, then “0 1 ”, then “0 1 2 ”).

  • Line 11: We initialize a StringBuilder, which maintains a mutable sequence of characters.

  • Line 13: The append() method adds characters directly to the existing internal buffer without creating new objects.

  • Line 15: We call toString() to convert the mutable sequence back into an immutable String for printing.

Working with StringBuilder

The StringBuilder class functions like a dynamic array for characters. We can initialize it with no arguments (default capacity) or provide an initial String or capacity. The most common operation is append(), which adds data to the end of the sequence.

StringBuilder is flexible; it can accept primitives, arrays, objects, and strings.

Java 25
public class BuilderBasics {
public static void main(String[] args) {
// Initialize with a starting string
StringBuilder message = new StringBuilder("Welcome");
// Append different data types
message.append(" to"); // Appends String
message.append(' '); // Appends char
message.append("Java "); // Appends String
message.append(25); // Appends int
System.out.println(message);
}
}
  • Line 3: We create a StringBuilder containing “Welcome”.

  • Line 6: We append the string “ to”. The internal buffer expands if necessary.

  • Line 7: We append a single character literal.

  • Line 9: We append an integer; StringBuilder automatically converts the number 21 to its string representation “25” before appending.

  • Line 11: Printing the object automatically calls its toString() method.

Modifying data (insert, delete, and reverse)

Unlike String, which requires complex substring operations to simulate editing, StringBuilder lets us insert, remove, or flip characters in place. This makes it ideal for tasks like formatting user input or parsing complex data.

  • insert(index, value): Adds characters at a specific position, shifting subsequent characters to the right.

  • delete(start, end): Removes characters from the start index up to (but not including) the end index.

  • deleteCharAt(index): Removes a single character.

  • reverse(): Reverses the sequence of characters in place.

Java 25
public class TextEditor {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("I love Python");
// 1. Replace "Python" with "Java" using delete and insert
int startIndex = sb.indexOf("Python");
sb.delete(startIndex, startIndex + 6); // Removes "Python"
sb.insert(startIndex, "Java"); // Inserts "Java"
System.out.println("After Edit: " + sb);
// 2. Reverse the string
sb.reverse();
System.out.println("After Reverse: " + sb);
}
}
  • Line 6: We locate the starting index of the word “Python”.

  • Line 7: delete removes characters from index 7 to 13. The buffer now holds “I love ”.

  • Line 8: insert places “Java” at index 7. The buffer now holds “I love Java”.

  • Line 13: reverse flips the entire sequence to “avaJ evol I”.

Method chaining (fluent API)

Most methods in StringBuilder return the StringBuilder object itself (this). This design pattern, known as a fluent API, allows us to chain multiple method calls into a single, readable line of code.

Instead of writing separate statements for each operation, we can combine them.

Java 25
public class FluentBuilder {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
String result = sb.append("Java")
.append(" ")
.append(25)
.reverse()
.toString();
System.out.println(result);
}
}
  • Line 5: sb.append("Java") adds “Java” and returns the sb object.

  • Line 6: The next .append(" ") is called on that same returned object.

  • Line 7: The .append(25) method accepts an integer, converts it to a string representation, and adds it to the sequence, resulting in “Java 25”.

  • Line 8: .reverse() flips “Java 25” to “52 avaJ”.

  • Line 9: Finally, .toString() converts the builder into a standard String for the assignment.

StringBuffer and thread safety

Java provides another class called StringBuffer. It offers the exact same methods as StringBuilder (append, insert, delete, etc.), but with one critical difference: thread safety.

Methods in StringBuffer are synchronized. This means that if multiple threads try to modify a StringBuffer at the same time, Java ensures that only one thread can access it at a time, preventing data corruption. However, this safety comes with a performance cost. Synchronization requires extra processing overhead, making StringBuffer slower than StringBuilder.

In modern Java development, we rarely use StringBuffer because we usually keep text buffers local to a single thread.

Java 25
public class LegacyBuffer {
public static void main(String[] args) {
// Usage is identical to StringBuilder
StringBuffer safeBuffer = new StringBuffer("Thread-safe ");
safeBuffer.append("sequence");
System.out.println(safeBuffer);
}
}
  • Line 4: We instantiate StringBuffer instead of StringBuilder.

  • Line 6: The append operation works exactly the same way but is internally synchronized.

Choosing between String, StringBuilder, and StringBuffer

Choosing the correct text class is a common design decision. We can decide based on two factors: mutability (do we need to change it?) and thread usage (is it shared?).

The following guidelines help us choose the right tool:

  1. Use String when the value is constant or rarely changes. This includes string literals, constants, identifiers, and map keys. Immutability makes it safe and easy to cache.

  2. Use StringBuilder for almost all text manipulation. If you are building strings in a loop, formatting complex output, or modifying text within a method, this is the standard choice. It is fast and efficient.

  3. Use StringBuffer only in specific legacy scenarios or when a single text buffer must be shared and modified by multiple threads simultaneously. If the buffer is not shared, StringBuffer simply adds unnecessary overhead.

We have now unlocked the ability to manipulate text efficiently in Java. By switching from String concatenation to StringBuilder, we can construct complex data strings and process large amounts of text without filling memory with garbage. We also understand that while StringBuffer exists for thread-safe scenarios, StringBuilder is our default tool for performance.