Fundamentals 21 min read

Why You Should Stop Misusing String: Complete Guide to String, StringBuffer, and StringBuilder

This article thoroughly examines Java's String, StringBuffer, and StringBuilder, explaining their internal implementations, immutability, JDK 9 changes, performance characteristics, and best‑practice usage scenarios, while providing concrete code examples, benchmarks, and interview questions to help developers choose the right tool.

Java Tech Workshop
Java Tech Workshop
Java Tech Workshop
Why You Should Stop Misusing String: Complete Guide to String, StringBuffer, and StringBuilder

Introduction

In Java, string handling is ubiquitous, but many developers misuse String concatenation with +=, causing severe performance problems.

1. String – immutable secret

1.1 How String is implemented

String is a final class that holds a final char[] (or byte[] from JDK 9) called value. Once created, the array cannot be modified, so any apparent modification creates a new String object.

public final class String {
    private final char[] value;
}

Internal array is final → cannot be changed.

All "modifications" actually allocate a new String.

1.2 JDK 9 change

Before JDK 9 the value was a char[]. Since JDK 9 it is a byte[] plus a coder field, allowing Latin‑1 storage and halving memory for ASCII strings.

public final class String {
    private final byte[] value;
    private final byte coder; // LATIN1 or UTF16
}

Memory saving: Latin‑1 strings use half the memory.

Performance boost: less GC pressure, better cache hits.

The change is transparent to developers; existing code continues to work.

1.3 Cost of immutability

Each concatenation or substring creates a new object. Example:

String s = "hello";
s = s + " world"; // creates a new String, old one discarded

In a loop of 10 000 iterations, nearly 100 000 objects are created:

String result = "";
for (int i = 0; i < 10000; i++) {
    result += i; // each iteration creates a new String
}

Answer: almost 100 000 objects.

1.4 String constant pool

When a literal is used, the JVM checks the constant pool. If the same literal exists, the same reference is returned; otherwise a new String is created and placed in the pool.

String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s3)); // true

The pool saves memory by sharing identical literals.

1.5 intern() method

Runtime‑generated strings can be added to the pool with intern():

String s1 = new String("hello") + new String("world");
s1.intern(); // puts "helloworld" into the pool
String s2 = "helloworld";
System.out.println(s1 == s2); // true on JDK7+

Before JDK 7 the pool lived in PermGen; from JDK 7 onward it resides on the heap.

1.6 Advantages of String

Thread‑safe because immutable.

Can be used as Map key; hashCode is cached.

Works reliably with class loaders, resource paths, and as lock objects.

2. StringBuffer – thread‑safe mutable string

2.1 Implementation

public final class StringBuffer {
    char[] value; // mutable, no final
}

All public methods are synchronized , guaranteeing thread safety.

public synchronized StringBuffer append(String str) {
    // …
    return this;
}

2.2 Cost of synchronization

Lock acquisition/release on each method call.

Lock contention in multithreaded scenarios.

Memory barriers force cache invalidation.

Micro‑benchmark (single thread, 100 000 appends):

StringBuilder ~8 ms

StringBuffer ~15 ms (≈2× slower)

2.3 When to use StringBuffer

Multithreaded environments where the same buffer is shared.

Scenarios requiring atomic string operations, e.g., log collectors.

public class LogCollector {
    private StringBuffer buffer = new StringBuffer();

    public synchronized void appendLog(String log) {
        buffer.append(log).append("
");
    }

    public synchronized String flush() {
        String result = buffer.toString();
        buffer.setLength(0);
        return result;
    }
}

3. StringBuilder – high‑performance mutable string

3.1 Implementation

public final class StringBuilder {
    char[] value; // mutable
}

Methods are not synchronized, so no locking overhead.

3.2 Performance comparison

Simple benchmark (100 000 iterations):

// String (very slow, seconds, may OOM)
// StringBuffer (~10‑20 ms)
// StringBuilder (~5‑10 ms) – up to 1000× faster than String

3.3 Why it is fast

No lock – eliminates synchronization cost.

Mutable char[] – modifies the existing array.

Efficient growth: capacity roughly doubles.

3.4 Chaining

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

4. How to choose

Guidelines:

String literals → use String (immutable, safe, can be a map key).

Heavy single‑thread concatenation → use StringBuilder (best performance).

Shared mutable buffer in multithreaded code → use StringBuffer.

When the string is a method return value, a key in a map, or a lock object → use String.

4.2 Code refactoring example

Before (inefficient):

public String getUserInfo(User user) {
    String info = "用户信息:";
    info += "姓名:" + user.getName();
    info += ",年龄:" + user.getAge();
    // …
    return info;
}

After (efficient):

public String getUserInfo(User user) {
    StringBuilder sb = new StringBuilder("用户信息:");
    sb.append("姓名:").append(user.getName())
      .append(",年龄:").append(user.getAge());
    // …
    return sb.toString();
}

4.3 Real‑world case: logging

High‑frequency logging with string concatenation created tens of thousands of temporary objects per second, causing high CPU usage. Replacing concatenation with placeholder logging eliminated the overhead.

// Bad
logger.info("用户" + userId + "下单成功,订单号:" + orderId + ",金额:" + amount);

// Good
logger.info("用户{}下单成功,订单号:{},金额:{}", userId, orderId, amount);

4.4 Alibaba Java coding guidelines

Mandatory – In loops, use StringBuilder.append . Mandatory – Use StringBuffer only when thread safety is required; otherwise prefer StringBuilder . Recommendation – Use String.charAt instead of regex for simple matches.

5. Underlying mechanisms

5.1 Expansion algorithm

When capacity is insufficient, StringBuilder expands roughly by 2×.

void expandCapacity(int minimumCapacity) {
    int newCapacity = (value.length + 1) * 2; // roughly double
    if (newCapacity < minimumCapacity) {
        newCapacity = minimumCapacity;
    }
    char[] newValue = new char[newCapacity];
    System.arraycopy(value, 0, newValue, 0, count);
    value = newValue;
}

Initial capacity is 16.

Growth factor reduces the number of reallocations.

Each expansion copies the array, incurring some cost.

5.2 Pre‑estimating capacity

Providing an estimated final length avoids repeated expansions:

StringBuilder sb = new StringBuilder(1024);
for (String data : largeDataList) {
    sb.append(data);
}

5.3 toString() caching

StringBuilder

caches the char[] after the first toString() call; the cache is invalidated by any mutating operation.

private transient char[] toStringCache;
public String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true); // shares the same char[]
}

5.4 reverse(), delete(), insert(), replace()

StringBuilder provides many mutable operations, e.g., reverse:

StringBuilder sb = new StringBuilder("hello");
sb.reverse(); // "olleh"

6. Best practices

6.1 Five ways to concatenate strings

"+" operator – fine for a few literals.

String.concat()
StringBuilder.append()
StringBuffer.append()
String.join()

(JDK 8+)

Performance order (outside loops): +concatStringBuilder > StringBuffer.

Recommendations:

Simple concatenation → + or String.join.

Multiple appends → StringBuilder.

Multithreaded shared buffer → StringBuffer.

6.2 Correct use of StringUtils

// Empty check
StringUtils.isEmpty(""); // true
StringUtils.isBlank("   "); // true

// Safe concatenation
StringUtils.defaultString(null, "default"); // "default"

// Reverse
StringUtils.reverse("hello"); // "olleh"

6.3 String splitting pitfalls

split()

uses regex and can be costly. For high‑frequency splitting, prefer StringTokenizer or manual indexOf parsing.

6.4 Common performance traps

// Bad: concatenation in a loop
String result = "";
for (String item : list) {
    result = result + item; // creates many temporary Strings
}

// Good: use StringBuilder
StringBuilder sb = new StringBuilder();
for (String item : list) {
    sb.append(item);
}
String result = sb.toString();

6.5 String comparison best practice

// Wrong
if (str1 == str2) { /* compares references */ }

// Correct
if (str1.equals(str2)) { /* compares content */ }

// Null‑safe
if ("constant".equals(str)) { /* safe */ }

7. Interview questions

7.1 Is String a primitive type?

No. Java primitives are byte, short, int, long, float, double, char, boolean. String is an object.

7.2 Differences between String, StringBuffer, StringBuilder

Mutability: String immutable; StringBuffer & StringBuilder mutable.

Thread safety: String safe; StringBuffer synchronized; StringBuilder not safe.

Performance (concatenation): String (slow) < StringBuffer < StringBuilder (fastest).

Underlying storage: String uses final char[] (or byte[] after JDK 9); the others use mutable char[].

7.3 How many objects does new String("hello") create?

Either one or two objects: one in the heap, plus possibly one in the constant pool if the literal is not already present.

7.4 Why is String immutable?

String is declared final .

Internal array is final .

No method modifies the internal array.

All modifying operations return a new String.

7.5 Why cache hashCode?

String is frequently used as a HashMap key; caching avoids recomputing the hash for an immutable object, improving map performance.

Conclusion

Understanding the trade‑offs among String, StringBuilder, and StringBuffer lets you write Java code that is up to a thousand times faster and far more memory‑efficient. Remember: use String for constants, StringBuilder for single‑thread concatenation, and StringBuffer only when thread‑safe mutable buffers are required.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaperformanceinterviewStringmemoryStringBuilderStringBuffer
Java Tech Workshop
Written by

Java Tech Workshop

Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.