Back to Blog
Java

Java StringBuilder vs StringBuffer: Which to Use

java stringbuilder vs stringbuffer: Understand the differences between StringBuilder and StringBuffer in Java, including thread safety, performance, and when to choose...

StringBuilderStringBufferthread-safetyperformancemutable-string
Diagram comparing StringBuilder and StringBuffer with thread safety and performance differences.

When working with mutable strings in Java, the choice between java stringbuilder vs stringbuffer comes down to one fundamental difference: thread safety. StringBuilder is not synchronized, while StringBuffer is. That single distinction affects performance, usage, and the situations where each class is appropriate.

The Core Difference: Synchronization

StringBuffer has been part of Java since version 1.0. Its methods are declared synchronized, meaning only one thread can execute a method on the same instance at a time. StringBuilder, introduced in Java 5, is a drop-in replacement with the same API but without synchronization. The absence of synchronized keywords removes lock acquisition and release overhead for every method call.

This difference is the reason StringBuilder is almost always faster in single-threaded code. The performance gap grows with the number of operations because each append, insert, or delete on a StringBuffer incurs monitor overhead.

How StringBuilder Works

StringBuilder maintains an internal character array. When the array is full, it creates a new, larger array and copies the existing characters. The default capacity is 16, but you can specify an initial capacity to reduce reallocations. The class is designed for building strings incrementally without creating intermediate String objects.

StringBuilder sb = new StringBuilder(); sb.append("Hello"); sb.append(" "); sb.append("world"); String result = sb.toString();

Each append modifies the internal buffer. The toString method creates a new String from the current contents. This is efficient because it avoids the quadratic cost of repeated string concatenation with + in a loop.

How StringBuffer Works

StringBuffer offers the same methods and behavior as StringBuilder. The only difference is that its public methods are synchronized. This makes it safe to share a single instance across multiple threads without external locking.

StringBuffer sb = new StringBuffer(); sb.append("Hello"); sb.append(" "); sb.append("world"); String result = sb.toString();

In practice, the synchronization is coarse-grained: each method call is atomic, but a sequence of calls is not. For example, two threads calling append in an interleaved order can still produce unexpected results if the logic depends on the order of operations.

Performance Considerations

Because StringBuilder does not acquire a lock, it is faster in single-threaded scenarios. The exact difference depends on the JVM and the number of operations, but the overhead is real. In a loop that appends thousands of characters, the cumulative cost of synchronization becomes measurable.

Consider a loop that builds a string from a list:

List<String> items = List.of("a", "b", "c"); StringBuilder sb = new StringBuilder(); for (String item : items) { sb.append(item); }

Using StringBuffer here would add unnecessary lock operations. Since the loop is local and no other thread can access the buffer, StringBuilder is the correct choice.

When to Use StringBuilder

Use StringBuilder in any single-threaded context. This includes:

  • Building strings inside a method or a local scope.
  • Constructing query strings or JSON payloads in a request handler.
  • Accumulating output in a loop where no other thread references the buffer.

In these cases, StringBuilder is faster and has no downside. The Java compiler itself uses StringBuilder for string concatenation expressions that involve multiple parts, such as a + b + c.

When to Use StringBuffer

StringBuffer is appropriate when the same mutable string instance is shared across multiple threads and every operation must be atomic. For example, a shared log buffer that multiple threads append to without external synchronization.

public class SharedLog { private final StringBuffer buffer = new StringBuffer(); public void log(String message) { buffer.append(message).append('\n'); } }

Here, each append is atomic, so concurrent calls do not corrupt the internal state. However, the compound operation append(message).append('\n') is not atomic as a whole; another thread can interleave between the two calls. If that is a problem, you need a higher-level lock.

Thread Safety in Practice

Even with StringBuffer, thread safety is limited to individual method calls. If you need to read the buffer's content while another thread writes, you still need external synchronization. For most applications, the synchronization in StringBuffer is either insufficient or unnecessary.

In modern Java, StringBuilder is the default choice. StringBuffer remains for legacy code or rare cases where the coarse-grained synchronization is exactly what you need. The StringBuilder class is not a drop-in replacement when you rely on the thread safety of StringBuffer; you must explicitly manage synchronization if you switch.

A Practical Comparison

The following table summarizes the key differences:

CriterionStringBuilderStringBuffer
Thread-safeNoYes (synchronized methods)
PerformanceFasterSlower due to locking
IntroducedJava 5Java 1.0
Typical useSingle-threadedMulti-threaded shared buffer
API compatibilitySame as StringBufferSame as StringBuilder

Choosing an Initial Capacity

Both classes allow you to set an initial capacity. If you know the approximate final length, specify it to avoid reallocations.

StringBuilder sb = new StringBuilder(256);

This is especially important in performance-sensitive code. Each reallocation copies the entire existing array, which is O(n) work. Pre-sizing reduces the number of copies.

Common Misconception: StringBuffer Is Always Safe

A common mistake is assuming StringBuffer makes compound operations thread-safe. For example, checking the length and then appending is not atomic:

if (buffer.length() == 0) { buffer.append("first"); }

Another thread can append between the length() call and the append call. The synchronized methods do not protect this check-then-act sequence. If you need that level of atomicity, use explicit locks or a different data structure.

Modern Java and StringBuilder

Since Java 5, StringBuilder has been the recommended class for all new code. The Java compiler itself uses StringBuilder when translating string concatenation. For example, "a" + b + "c" is compiled to a StringBuilder sequence. This means even if you write +, the runtime uses StringBuilder internally.

StringBuffer is still supported for backward compatibility, but it is rarely the best choice. If you find yourself reaching for StringBuffer, ask whether the instance is truly shared across threads and whether the method-level synchronization is sufficient. In most cases, StringBuilder with explicit synchronization (if needed) gives you more control and better performance.

Final Technical Consideration: Capacity Growth

When the internal array is full, both classes expand it. The new capacity is roughly double the old capacity plus two. This growth strategy balances memory usage and reallocation frequency. If you are building a very large string, pre-sizing can avoid many copies. For example, if you know you will append 10,000 characters, create the buffer with capacity 10,000. This is a simple optimization that works for both StringBuilder and StringBuffer and is often overlooked.

java stringbuilder vs stringbuffer: Practical Usage and Code | RYUSLOG DEV