Back to String & String Pool
Q8Question 8 of 12

When would you specifically need StringBuffer over StringBuilder?

Key Concepts
  • Almost never in modern Java – StringBuilder + thread-local design is preferred
  • StringBuffer only when multiple threads SHARE the same mutable string builder
  • Even then, higher-level synchronization or thread-local builders are cleaner
  • Legacy code may use StringBuffer – do not change to StringBuilder without analyzing thread safety
  • If in doubt, use StringBuilder

Definition

Almost never. Use StringBuilder by default. StringBuffer is only needed when multiple threads share the same builder instance, which is rare and usually a design smell.

Decision:

  • StringBuilder: Default choice. Single-threaded or thread-local building.
  • StringBuffer: Only when the exact same builder instance is accessed by multiple threads simultaneously.
  • Better alternative: Each thread builds with its own StringBuilder. No sharing, no synchronization.

When do you need a shared whiteboard with a lock (StringBuffer)? Almost never. Give each person their own whiteboard (StringBuilder). Faster and simpler.

Understanding the Concepts

The rare case for StringBuffer

JAVA
// RARE: shared log buffer (legacy pattern)
StringBuffer sharedLog = new StringBuffer();

// Thread 1: sharedLog.append("TX001 success\n");
// Thread 2: sharedLog.append("TX002 failed\n");
// StringBuffer ensures no data corruption

But modern code uses proper logging frameworks (SLF4J) instead of shared string buffers.

The modern alternative

JAVA
// Each thread builds independently, merge at the end
List<String> results = accounts.parallelStream()
    .map(a -> new StringBuilder()
        .append(a.getAccountId()).append(": ").append(a.getBalance())
        .toString())
    .collect(Collectors.toList());

What should a senior engineer know?

  • StringBuffer is legacy. New code should never use it.
  • If you see StringBuffer in a PR, ask: "Is this builder actually shared across threads?" Usually, it is not.
  • ConcurrentLinkedQueue is a better thread-safe alternative for collecting strings from multiple threads.

What are common mistakes?

A frequent mistake is using == to compare String content instead of equals().

Wrong approach:

JAVA
package com.bank.service;

public class AccountValidator {
    public boolean validateAccountType(String type) {
        // WRONG: == compares references, not content
        if (type == "SAVINGS") {
            return true;
        }
        return false;
    }
}

Correct approach:

JAVA
package com.bank.service;

public class AccountValidator {
    public boolean validateAccountType(String type) {
        // CORRECT: equals() compares content
        if ("SAVINGS".equals(type)) {  // null-safe: literal first
            return true;
        }
        return false;
    }
}

Using the literal on the left side of equals() prevents NullPointerException when the variable is null. This is a standard practice at National Bank of India for all string comparisons in service code.

What are the edge cases?

String behavior has several surprising edge cases that can catch banking developers off guard.

JAVA
package com.bank.edge;

public class StringEdgeCases {
    public static void main(String[] args) {
        // Edge case 1: compile-time constant folding
        String a = "SAV" + "2001";       // Compile-time constant
        String b = "SAV2001";
        System.out.println(a == b);       // true (same pool entry)

        // Edge case 2: variable concatenation is NOT pooled
        String prefix = "SAV";
        String c = prefix + "2001";       // Runtime concatenation
        System.out.println(b == c);       // false (different objects)

        // Edge case 3: final variable IS a compile-time constant
        final String finalPrefix = "SAV";
        String d = finalPrefix + "2001";  // Compile-time constant
        System.out.println(b == d);       // true (same pool entry)

        // Edge case 4: substring no longer shares char array (Java 7u6+)
        String full = "AccountNumber:SAV2001";
        String sub = full.substring(14); // New String, new char array
    }
}
TEXT
true
false
true

The final keyword makes the variable a compile-time constant, which changes how the compiler handles concatenation.

What are common interview follow-up questions?

Interviewers often follow up with these related questions:

  • How does this concept apply to a high-throughput banking transaction system?
  • What are the performance implications in a production environment?
  • How would you debug issues related to this concept?
  • What tools would you use to monitor this in production?
  • How does this behave differently in Java 8 vs Java 17+?

When answering follow-up questions, always relate your answer back to practical experience. For example, at National Bank of India, Rajesh debugged a production issue related to this concept by using JFR recordings and heap dumps. Providing concrete examples from banking systems shows the interviewer that you understand both the theory and the practice.

Visual Overview

DIAGRAM
  Need string building?
    ├── Single thread? → StringBuilder (default)
    ├── Multi-thread, separate builders? → StringBuilder (each thread has its own)
    └── Multi-thread, SHARED builder? → StringBuffer (rare, usually wrong design)
                                         Better: thread-local builders + merge

Key Takeaway

Use StringBuilder by default. StringBuffer is needed only when multiple threads share the same builder instance, which is rare and usually a design problem. Modern code uses thread-local builders or proper logging frameworks instead.

One sentence to remember: StringBuilder always, StringBuffer almost never – give each thread its own builder.

---

Share
Use ← → arrow keys to navigate