JB Header

String vs StringBuilder vs StringBuffer in Java

Introduction Because a String can never be changed, every "change" creates a new object. That is fine for a few joins, but building a long string piece by piece - in a loop, say - creates thousands of throwaway objects. Java has two classes made for building text: StringBuilder and StringBuffer. This tutorial explains String vs StringBuilder vs StringBuffer in Java. We will start with the problem of joining strings in a loop. We will then use StringBuilder and its most useful methods - append(), insert(), delete(), reverse() and more. Next we will measure the speed difference. Lastly, we will see how StringBuffer differs, and compare all three classes side by side.

(Note - This tutorial builds on Java String Basics and ImmutabilityClick to read tutorial: Java String Basics and Why Strings Are Immutable.) The problem with joining strings in a loop result = result + piece; does not add to the existing string. It creates a new String, copies all the characters of result into it, then adds piece. In a loop, each copy is longer than the last - so building a string of n pieces copies roughly n²/2 characters in total. For a few pieces nobody notices; for tens of thousands, it becomes very slow.

(For a single expression such as "Hello, " + name + "!", the compiler already generates efficient code, so + is the right choice there. The problem is only += repeated in a loop.) StringBuilder - a mutable string A StringBuilder holds a mutable sequence of characters in an internal buffer, which grows as needed. Its methods change that buffer directly, without creating new objects, and most of them return the same StringBuilder, so calls can be chained. When the text is complete, toString() creates the final String.
Building a string with StringBuilder
//BuilderBasics.java
public class BuilderBasics {
  public static void main(String[] args) {
    StringBuilder sb = new StringBuilder();
    for (int i = 1; i <= 5; i++) {
      sb.append(i);
      if (i < 5) {
        sb.append(", ");
      }
    }
    String numbers = sb.toString();
    System.out.println(numbers);

    String receipt = new StringBuilder()
        .append("Item: ").append("Java book")
        .append(" | Qty: ").append(2)
        .append(" | Paid: ").append(true)
        .toString();
    System.out.println(receipt);
  }
}
 OUTPUT of the above code
1, 2, 3, 4, 5
Item: Java book | Qty: 2 | Paid: true
append() accepts any type - strings, numbers, characters, booleans and objects - and converts it to text, just like +.
Useful StringBuilder methods
  • append(x) - adds x to the end.
  • insert(index, x) - inserts x at index.
  • delete(start, end) and deleteCharAt(index) - remove characters.
  • replace(start, end, str) - replaces a range of characters.
  • reverse() - reverses the characters.
  • setCharAt(index, ch), charAt(index), length(), indexOf(str) - work like their String versions.
  • setLength(n) - cuts the text to n characters; setLength(0) empties the builder so it can be reused.
StringBuilder methods
//BuilderMethods.java
public class BuilderMethods {
  public static void main(String[] args) {
    StringBuilder sb = new StringBuilder("Hello World");
    sb.insert(5, ",");
    System.out.println("insert      : " + sb);
    sb.append("!");
    System.out.println("append      : " + sb);
    sb.replace(7, 12, "Java");
    System.out.println("replace     : " + sb);
    sb.delete(5, 6);
    System.out.println("delete      : " + sb);
    sb.setCharAt(0, 'J');
    System.out.println("setCharAt   : " + sb);
    sb.deleteCharAt(sb.length() - 1);
    System.out.println("deleteCharAt: " + sb + " (length " + sb.length() + ")");
    sb.reverse();
    System.out.println("reverse     : " + sb);
    sb.setLength(0);
    System.out.println("setLength(0): [" + sb + "]");

    String word = "racecar";
    boolean palindrome = new StringBuilder(word).reverse().toString().equals(word);
    System.out.println(word + " is a palindrome: " + palindrome);
  }
}
 OUTPUT of the above code
insert      : Hello, World
append      : Hello, World!
replace     : Hello, Java!
delete      : Hello Java!
setCharAt   : Jello Java!
deleteCharAt: Jello Java (length 10)
reverse     : avaJ olleJ
setLength(0): []
racecar is a palindrome: true
Explanation of the code
  • Every method changed the same sb object, step by step. Ranges work like substring() - start is included and end is not.
  • Printing a StringBuilder calls its toString(), so println(sb) shows its current text.
  • new StringBuilder(word).reverse().toString() is the classic one-line way to reverse a string, since String has no reverse() method.
  • Compare with toString(): StringBuilder does not override equals(), so two builders with the same text are not equals(). Convert to String first, as explained in == vs equals() vs compareTo()Click to read tutorial: Java String Comparison: == vs equals() vs compareTo().
Measuring the difference Let us build a string of 50,000 numbers, once with += on a String and once with a StringBuilder.
String += vs StringBuilder in a loop
//ConcatTiming.java
public class ConcatTiming {
  public static void main(String[] args) {
    int n = 50_000;

    long start = System.nanoTime();
    String s = "";
    for (int i = 0; i < n; i++) {
      s += i;
    }
    long stringMillis = (System.nanoTime() - start) / 1_000_000;

    start = System.nanoTime();
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < n; i++) {
      sb.append(i);
    }
    String built = sb.toString();
    long builderMillis = (System.nanoTime() - start) / 1_000_000;

    System.out.println("Same result: " + s.equals(built) + ", length " + built.length());
    System.out.println("String +=      : " + stringMillis + " ms");
    System.out.println("StringBuilder  : " + builderMillis + " ms");
  }
}
 OUTPUT of the above code (timings vary from machine to machine)
Same result: true, length 238890
String +=      : 710 ms
StringBuilder  : 11 ms
Both produced the same 238,890-character string, but the StringBuilder was many times faster. The gap grows quickly as n grows, because += copies the ever-longer string on every pass.
StringBuffer - the thread-safe version StringBuffer has the same methods as StringBuilder, and is used in exactly the same way. The difference is that its methods are synchronized - only one thread at a time can run them on a given object - which makes a single StringBuffer safe to share between threads, at the cost of some speed.

StringBuffer is the older class, part of Java since version 1.0. StringBuilder was added in Java 5 as a faster replacement for the common case where only one thread builds the string. In practice, strings are almost always built by one thread, so use StringBuilder. You will still see StringBuffer in older code and some older APIs.
StringBuffer has the same API
//BufferDemo.java
public class BufferDemo {
  public static void main(String[] args) {
    StringBuffer buffer = new StringBuffer("Java");
    buffer.append(" 8").insert(0, "Learn ").append(" and beyond");
    System.out.println(buffer);
    System.out.println("Capacity: " + buffer.capacity() + ", length: " + buffer.length());
  }
}
 OUTPUT of the above code
Learn Java 8 and beyond
Capacity: 42, length: 23
Capacity is the size of the internal buffer, which grows automatically. If you know roughly how long the final text will be, new StringBuilder(expectedLength) creates a buffer of that size up front and avoids regrowing it. String vs StringBuilder vs StringBuffer - comparison
StringStringBuilderStringBuffer
Mutable?No - immutableYesYes
Thread-safe?Yes (because immutable)NoYes (synchronized methods)
Speed for many changesSlow - new object each timeFastestFast, slightly slower than StringBuilder
SinceJava 1.0Java 5Java 1.0
equals() compares content?YesNoNo
Use forFixed text, map keys, values passed aroundBuilding or editing text, especially in loopsBuilding text shared between threads (rare); older code

Rule of thumb: use String for values, + for joining in a single expression, and StringBuilder whenever you build text in a loop or in many steps. Summary In this tutorial we first saw why joining strings with += in a loop is slow - each pass copies the whole string into a new object. We then used StringBuilder, a mutable string, and its methods append(), insert(), replace(), delete(), reverse() and setLength(). Next we measured how much faster StringBuilder is. We then saw that StringBuffer is the older, synchronized version with the same methods. Lastly, we compared all three classes. In the next tutorial, we will write multi-line strings with text blocksClick to read tutorial: Java Text Blocks: Multi-line Strings with Examples.