StringBuilder in Java

  • Last Updated: February 1, 2025
  • By: javahandson
  • Series
img

StringBuilder in Java

A StringBuilder in Java is a class used to create and manipulate mutable sequences of characters. Unlike mutable String, StringBuilder allows modifications to its contents without creating a new object every time a change is made. This makes it more efficient for scenarios where a lot of string manipulation, such as appending, deleting, or inserting characters is required.

 

Key features of StringBuilder

1. Mutability

StringBuilder objects are mutable, meaning their content can be changed after creation. Unlike String, modifications like appending or replacing characters do not create a new object.

package com.java.handson.strings;

public class StringBuilderDemo {
    public static void main(String[] args) {

        String str = "Java";
        System.out.println("Memory location of str: " + Integer.toHexString(System.identityHashCode(str)));

        str += " HandsOn";  // Creates a new String object
        System.out.println("Memory location of str: " + Integer.toHexString(System.identityHashCode(str)));

        System.out.println(str);

        StringBuilder stringBuilder = new StringBuilder("Java");
        System.out.println("Memory location of stringBuilder: " + Integer.toHexString(System.identityHashCode(str)));

        stringBuilder.append(" HandsOn");  // Modifies the same StringBuffer object
        System.out.println("Memory location of stringBuilder: " + Integer.toHexString(System.identityHashCode(str)));

        System.out.println(stringBuilder);
    }
}
Output:
Memory location of str: 16d3586
Memory location of str: 154617c
Java HandsOn
Memory location of stringBuilder: 154617c
Memory location of stringBuilder: 154617c
Java HandsOn
   

In the above program, we can see str points to different memory locations when a new string is appended whereas in the case of StringBuilder, the same object gets modified and prints the same memory location.

2. Not Thread-Safe

StringBuilder is not synchronized, which means it is not thread-safe. Multiple threads working on the same StringBuilder object can lead to data corruption. However, this also makes StringBuilder faster compared to StringBuffer which is synchronized.

In the below program, we will see what happens if multiple threads try to update the same StringBuilder object.

package com.java.handson.strings;

public class StringBuilderDemo {

    public static void main(String[] args) {

        // Shared StringBuilder instance
        StringBuilder sb = new StringBuilder("Java");

        // Thread 1: Appends to the StringBuilder
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                sb.append(" HandsOn");
                System.out.println("Thread 1: " + sb);
            }
        });

        // Thread 2: Reverses the StringBuilder
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                sb.reverse();
                System.out.println("Thread 2: " + sb);
            }
        });

        // Start both threads
        thread1.start();
        thread2.start();
    }
}
Output:
Thread 1: Java HandsOn
Thread 1: nOsdnaH avaJ HandsOn
Thread 1: nOsdnaH avaJ HandsOn HandsOn
Thread 2: nOsdnaH avaJ
Thread 2: nOsdnaH nOsdnaH Java HandsOn
Thread 2: nOsdnaH avaJ HandsOn HandsOn

In the above example, multiple threads are trying to update the same StringBuilder object and as we know StringBuilder is not synchronized hence it leads to data corruption and we are seeing uneven output.

Now we will rewrite the above program where the append and reverse operations are synchronized for thread safety.

package com.java.handson.strings;

public class StringBuilderDemo {

    public static void main(String[] args) {

        // Shared StringBuilder instance
        StringBuilder sb = new StringBuilder("Java");

        // Thread 1: Appends to the StringBuilder
        Thread thread1 = new Thread(() -> {
            synchronized (sb) {
                for (int i = 0; i < 3; i++) {
                    sb.append(" HandsOn");
                    System.out.println("Thread 1: " + sb);
                }
            }
        });

        // Thread 2: Reverses the StringBuilder
        Thread thread2 = new Thread(() -> {
            synchronized (sb) {
                for (int i = 0; i < 3; i++) {
                    sb.reverse();
                    System.out.println("Thread 2: " + sb);
                }
            }
        });

        // Start both threads
        thread1.start();
        thread2.start();
    }
}
Output:
Thread 1: Java HandsOn
Thread 1: Java HandsOn HandsOn
Thread 1: Java HandsOn HandsOn HandsOn
Thread 2: nOsdnaH nOsdnaH nOsdnaH avaJ
Thread 2: nOsdnaH nOsdnaH nOsdnaH avaJ
Thread 2: nOsdnaH nOsdnaH nOsdnaH avaJ

In the above program, the output will switch between the threads in an orderly manner, ensuring consistency without any corruption.

3. Dynamic Capacity Management

StringBuilder manages its capacity dynamically. If the number of characters exceeds the current capacity, it automatically expands to accommodate more characters. We can use the ensureCapacity() method to manage this explicitly.

4. Efficient Performance

StringBuilder is more efficient for frequent modifications like concatenation, insertion, or deletion compared to String, which creates new objects for every change.

5. Supports Chaining of Methods

StringBuilder methods like append(), insert(), delete(), etc., return the same StringBuilder object, allowing method chaining for cleaner and more concise code.

StringBuilder sb = new StringBuilder("Hello");

sb.append(" World").insert(6, "Java ").reverse();

System.out.println(sb); // Output: dlroW avaJ olleH

Constructors of StringBuilder

1. StringBuilder() – Creates an empty StringBuilder with an initial capacity of 16 characters.

StringBuilder sb = new StringBuilder();

sb.append("Java");

System.out.println(sb.capacity()); // Output: 16

2. StringBuilder(int capacity) – Creates an empty StringBuilder with the specified initial capacity.

StringBuilder sb = new StringBuilder(50);

sb.append("Java");

System.out.println(sb.capacity()); // Output: 50

3. StringBuilder(String str) – Creates a StringBuilder with an initial content of the specified string and a capacity of the length of the string + 16.

StringBuilder sb = new StringBuilder("Java");

System.out.println(sb.capacity()); // Output: 20

4. StringBuilder(CharSequence seq) – Creates a StringBuilder with an initial content of the specified CharSequence (e.g., String, StringBuffer, etc.).

CharSequence seq = "Java HandsOn";

StringBuilder sb = new StringBuilder(seq);

System.out.println(sb); // Output: Java HandsOn

Common methods of StringBuilder

The StringBuilder class provides several methods to perform operations like modifying, appending, deleting, and reversing strings. Here are some of the most commonly used methods of StringBuilder:

1. append(String str) – Appends the specified string to the end of the current StringBuilder.

StringBuilder sb = new StringBuilder("Java");

sb.append(" HandsOn");

System.out.println(sb); // Output: Java HandsOn

2. insert(int offset, String str) – Inserts the specified string at the given index.

StringBuilder sb = new StringBuilder("Java");

sb.insert(2, "HandsOn");

System.out.println(sb); // Output: JaHandsOnva

3. replace(int start, int end, String str) – Replace the characters in the specified range with the given string.

StringBuilder sb = new StringBuilder("Java Hello");

sb.replace(5, 10, "HandsOn");

System.out.println(sb); // Output: Java HandsOn

4. delete(int start, int end) – Removes the characters in the specified range.

StringBuilder sb = new StringBuilder("Java HandsOn");

sb.delete(5, 10);

System.out.println(sb); // Output: Java On

5. deleteCharAt(int index) – Removes the character at the specified index.

StringBuilder sb = new StringBuilder("Java H");

sb.deleteCharAt(4);

System.out.println(sb); // Output: JavaH

6. reverse() – Reverses the sequence of characters in the StringBuilder.

StringBuilder sb = new StringBuilder("Java HandsOn");

sb.reverse();

System.out.println(sb); // Output: nOsdnaH avaJ

7. charAt(int index) – Returns the character at the specified index.

StringBuilder sb = new StringBuilder("Java HandsOn");

System.out.println(sb.charAt(5)); // Output: H

8. setCharAt(int index, char ch) – Sets the character at the specified index.

StringBuilder sb = new StringBuilder("Java H");

sb.setCharAt(5, 'J');

System.out.println(sb); // Output: Java J

9. capacity() – Returns the current capacity of the StringBuilder.

StringBuilder sb = new StringBuilder("Java");

System.out.println(sb.capacity()); // Output: 20 (length + 16)

10. length() – Returns the number of characters in the StringBuilder.

StringBuilder sb = new StringBuilder("Java HandsOn");

System.out.println(sb.length()); // Output: 12

11. setLength(int newLength) – Sets the length of the StringBuilder. If the new length is less than the current length it truncates the builder but if it’s more then it pads with null (\u0000).

StringBuilder sb = new StringBuilder("Java HandsOn");

sb.setLength(4);

System.out.println(sb); // Output: Java

sb.setLength(6);

System.out.println(sb); // Output: Java\u0000\u0000

12. substring(int start) – Extracts a complete substring from the specified range.

StringBuilder sb = new StringBuilder("Java HandsOn");

System.out.println(sb.substring(5)); // Output: HandsOn

13. substring(int start, int end) – Extracts a substring from a range that includes start but excludes end.

StringBuilder sb = new StringBuilder("Java HandsOn");

System.out.println(sb.substring(0, 4)); // Output: Java

14. ensureCapacity(int minimumCapacity) – Ensures that the StringBuilder has at least the specified capacity.

StringBuilder sb = new StringBuilder();

sb.ensureCapacity(50);

System.out.println("Capacity: " + sb.capacity()); // Output: 50

15. indexOf(String str) – Returns the index of the first occurrence of the specified string starting from the beginning index.

StringBuilder sb = new StringBuilder("Java HandsOn");

System.out.println(sb.indexOf("HandsOn")); // Output: 5

If the string is not found then #indexOf method returns -1.

StringBuilder sb = new StringBuilder("Java HandsOn");

System.out.println(sb.indexOf("Hello")); // Output: -1

16. indexOf(String str, int fromIndex) – Returns the index of the first occurrence of the specified string, starting from the given index.

StringBuilder sb = new StringBuilder("Java HandsOn HandsOn");

System.out.println(sb.indexOf("HandsOn", 6)); // Output: 13 ( picked the second HandsOn )

If the string is not found then #indexOf method returns -1.

StringBuilder sb = new StringBuilder("Java HandsOn HandsOn");

System.out.println(sb.indexOf("Hello", 6)); // Output: -1

17. lastIndexOf(String str) – Returns the index of the last occurrence of the specified string in the StringBuilder. If the string is not found, it returns -1.

StringBuilder sb = new StringBuilder("Java HandsOn Java");

int index = sb.lastIndexOf("Java");

System.out.println("Last occurrence of 'Java': " + index); // Output : 13
StringBuilder sb = new StringBuilder("Java HandsOn Java");

int index = sb.lastIndexOf("Hello");

System.out.println("Last occurrence of 'Hello': " + index); // Output : -1

18. lastIndexOf(String str, int fromIndex) – Searches for the last occurrence of the specified string starting from the given index and moving backward.

StringBuilder sb = new StringBuilder("Java HandsOn HandsOn");

int index = sb.lastIndexOf("HandsOn", 10); // Start searching from index 10

System.out.println("Last occurrence of 'HandsOn' before index 10: " + index); // Output : 5
StringBuilder sb = new StringBuilder("Java HandsOn HandsOn");

int index = sb.lastIndexOf("Hello", 10); // Start searching from index 10

System.out.println("Last occurrence of 'HandsOn' before index 10: " + index); // Output : -1

Memory management in StringBuilder

1. Initial Capacity – When a StringBuilder object is created, it reserves memory for an initial number of characters. The default initial capacity is 16 characters if no capacity is specified. If a string is passed to the constructor, the initial capacity becomes the following:

capacity = string length + 16

StringBuilder sb1 = new StringBuilder();  

System.out.println(sb1.capacity()); // Output: 16

StringBuilder sb2 = new StringBuilder("Java");  

System.out.println(sb2.capacity()); // Output: 20 (4 + 16)

2. Dynamic Resizing Mechanism – When the number of characters in the StringBuilder exceeds its current capacity, Java automatically resizes the buffer using this formula:

new capacity=(old capacity×2)+2

This exponential growth strategy reduces the number of memory reallocations making StringBuilder efficient.

StringBuilder sb = new StringBuilder(5); // Initial capacity: 5

sb.append("Java HandsOn");  // Exceeds capacity

System.out.println(sb.capacity()); // Output: 12 (5 * 2 + 2)

3. ensureCapacity(int minimumCapacity) – This method pre-allocates memory to avoid frequent resizing operations, which improves performance. If minimum capacity is less than or equal to the current capacity, nothing happens but if it’s greater, the capacity increases according to the resizing formula.

StringBuilder sb = new StringBuilder();

sb.ensureCapacity(50); // Ensures at least 50 characters can be stored

System.out.println(sb.capacity()); // Output: 50

4. trimToSize() for Memory Optimization – If a StringBuilder has extra unused memory, trimToSize() reduces the capacity to match the current length. This helps free up unnecessary memory usage.

StringBuilder sb = new StringBuilder(50);

sb.append("JavaHandsOn");

System.out.println(sb.capacity()); // Output: 50

sb.trimToSize();

System.out.println(sb.capacity()); // Output: 11

Q. How does exponential growth strategy reduce the number of memory reallocations making StringBuilder efficient?

Instead of increasing the capacity one character at a time (which would be slow and inefficient), StringBuilder doubles its capacity when needed. This reduces the number of times memory is allocated and copied. Consider a scenario where you append a large number of characters. Without exponential growth, Java would have to allocate new memory for every single append operation. With exponential growth, it minimizes memory copying, leading to faster execution.

 

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append("a");
}

Conclusion

StringBuilder is a powerful and efficient class in Java for handling dynamic string manipulations. Unlike String, which is immutable and creates new objects on modification, StringBuilder is mutable, allowing in-place modifications, making it significantly faster for frequent string operations.

Through this article, we explored:

  • What is StringBuilder? — Understanding its purpose and how it differs from String and StringBuffer.
  • Key Features — Its mutability, performance benefits, and lack of synchronization.
  • Constructors — Different ways to initialize a StringBuilder object with varying capacities.
  • Common Methods — Essential operations like append(), insert(), delete(), replace(), and reverse().
  • Memory Management — How StringBuilder dynamically resizes its buffer using an exponential growth strategy, reducing memory reallocations and improving performance.

For applications requiring frequent string modifications, StringBuilder is the recommended choice over String due to its efficiency, lower memory consumption, and faster execution. However, in multi-threaded environments, where thread safety is needed, StringBuffer might be a better alternative.

By understanding and leveraging StringBuilder effectively, developers can write more optimized and high-performing Java applications.

So this is all about the StringBuilder in Java. If you have any questions on this topic, please raise them in the comments section. If you liked this article then please share this post with your friends and colleagues.

Leave a Comment