Java
String
StringBuilder
Programming
Performance

String vs. StringBuilder

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

String and StringBuilder both work with text in Java, but they optimize for different constraints. String is immutable, which makes it safe and predictable, while StringBuilder is mutable and efficient for iterative text construction. Picking the wrong type in hot paths can create avoidable allocation and garbage collection overhead.

Why String Exists as an Immutable Type

A String value cannot change after creation. Concatenation creates a new object:

java
1String a = "Hello";
2String b = a + " world";
3System.out.println(a); // Hello
4System.out.println(b); // Hello world

Immutability brings major benefits:

  • thread-safe sharing without extra locking,
  • stable behavior when used as map keys,
  • compatibility with the JVM string pool.

Because the contents never change, two parts of your program can safely reference the same string instance.

Where StringBuilder Provides Real Gains

StringBuilder uses a growable internal buffer, so repeated appends reuse storage instead of creating many temporary objects.

java
1StringBuilder sb = new StringBuilder();
2for (int i = 0; i < 5; i++) {
3    sb.append("item-").append(i).append(',');
4}
5String result = sb.toString();
6System.out.println(result);

This pattern is usually much faster than repeated + inside loops, especially when building large payloads such as CSV rows, SQL statements, template output, or log lines.

Concatenation and Compiler Behavior

Not every + is a performance bug. The Java compiler often rewrites simple concatenations into builder logic under the hood.

java
String message = "User " + userId + " logged in";

For short expressions this is fine and often clearer. The problem appears when concatenation happens repeatedly in loops or recursive builders, where temporary object creation scales with iteration count.

Capacity Management in Hot Paths

If you can estimate final text size, pre-size the builder to reduce buffer resizing.

java
1int expectedChars = 4096;
2StringBuilder sb = new StringBuilder(expectedChars);
3for (int i = 0; i < 1000; i++) {
4    sb.append(i).append('\n');
5}
6System.out.println(sb.length());

Reducing reallocation can improve throughput and smooth latency under load.

Threading Considerations

StringBuilder is not synchronized. If multiple threads mutate one instance, behavior is unsafe. String avoids this issue because it is immutable.

If synchronized mutation is required, Java has StringBuffer, but modern code typically keeps builders thread-confined instead of sharing mutable state.

Example safe pattern:

java
1public String formatLine(int id, String name) {
2    StringBuilder sb = new StringBuilder(64);
3    sb.append("id=").append(id).append(",name=").append(name);
4    return sb.toString();
5}

Each call creates a local builder, so no cross-thread mutation occurs.

API Design Guidance

Expose immutable String values at module boundaries and use builders inside implementation details. This gives consumers a stable contract while preserving internal performance.

In other words:

  • input and output types should usually be String,
  • intermediate composition should usually be StringBuilder.

This keeps public APIs simple and avoids leaking mutable implementation details.

Measure with a Microbenchmark

When performance matters, run a benchmark that reflects your workload. A rough comparison:

java
1public class ConcatBench {
2    public static void main(String[] args) {
3        int n = 20000;
4
5        long t1 = System.nanoTime();
6        String s = "";
7        for (int i = 0; i < n; i++) {
8            s += i;
9        }
10        long t2 = System.nanoTime();
11
12        long t3 = System.nanoTime();
13        StringBuilder sb = new StringBuilder();
14        for (int i = 0; i < n; i++) {
15            sb.append(i);
16        }
17        String out = sb.toString();
18        long t4 = System.nanoTime();
19
20        System.out.println("String concat ms: " + (t2 - t1) / 1_000_000.0);
21        System.out.println("Builder concat ms: " + (t4 - t3) / 1_000_000.0);
22        System.out.println(out.length());
23    }
24}

For large n, the builder approach usually wins by a wide margin.

Common Pitfalls

  • Using + in large loops and generating excessive temporary strings.
  • Refactoring every short concatenation to StringBuilder, reducing readability without measurable gain.
  • Sharing one builder across threads and creating data races.
  • Forgetting to call toString() before returning final output.
  • Ignoring builder initial capacity in heavy text-generation paths.

Summary

  • 'String is immutable and ideal for stable, shareable text values.'
  • 'StringBuilder is mutable and better for iterative text construction.'
  • Short one-off concatenations with + are often acceptable.
  • Loop-heavy or large payload generation should favor StringBuilder.
  • Benchmark real workloads before making broad performance claims.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.