Backend Notes DEC 09, 2025 • 03 MIN READ

STRING VS
STRINGBUILDER VS
STRINGBUFFER

Abstract technical background

I still remember one night debugging a slow API in my Spring Boot project. Everything looked clean, proper caching, neat service layers, even decent SQL queries. But something felt off. The CPU spikes were weird, and memory usage kept creeping up.

StringExample.java JAVA
String response = "";
for (int i = 0; i < 10_000; i++) {
    response += i;
}

One tiny innocent-looking String concatenation inside a loop.
That’s when I finally understood: String, StringBuilder, and StringBuffer aren’t the same, and using the wrong one silently kills performance.

Let me share the lesson I wish someone had told me earlier.

The Truth About String: Immutable but Dangerous (Sometimes)

String in Java is immutable. Once created, it can’t change. Every time you “modify” a String, Java actually:

  • check_circle Creates a new object
  • check_circle Throws away the old one

Great for:

  • check_circle Constants
  • check_circle HTTP header values
  • check_circle Configuration keys
  • check_circle Simple text processing

A disaster for:

  • check_circle Loops that build dynamic text
  • check_circle Large JSON/XML/HTML generation
  • check_circle Repeated concatenation

In a Spring Boot app, I used String everywhere: logging, building responses, dynamic SQL, until I realized I was creating thousands of unnecessary objects.

Sample (Slow)

StringExample.java JAVA
String result = "";
for (int i = 0; i < 10000; i++) {
    result += i; // Creates new object every loop
}

This looks harmless but causes heavy GC pressure.

StringBuilder: The Partner You Actually Need

After that painful debugging session, I switched to StringBuilder, and suddenly everything felt lighter.

StringBuilder is:

  • check_circle Mutable
  • check_circle Fast
  • check_circle Non-thread-safe (and that’s usually perfectly fine in Spring Boot)

Most Spring Boot code runs in single-threaded request contexts, so there’s no shared mutation risk. That makes StringBuilder the best default for constructing dynamic text.

Sample (Fast)

StringBuilderExample.java JAVA
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append(i);
}
String result = sb.toString();

No object churn. No GC storms. Just clean performance.

StringBuffer: The Old Guard You Rarely Need

Then there’s StringBuffer. It behaves exactly like StringBuilder but adds synchronization, meaning it’s thread-safe.

Here’s the catch:

  • check_circle In modern Spring Boot apps, you almost never manually share a text builder across multiple threads.
  • check_circle If you do, the design is usually questionable.

StringBuffer is a relic from early Java days think pre-Spring era. But yes, if you absolutely need safe mutation across threads, it’s there.

StringBufferExample.JAVA JAVA
StringBuffer sb = new StringBuffer();
sb.append("Sawit adalah");
sb.append(" pohon juga");

Slowest among the three, but safe.

How Spring Boot Fits Into This Story

When building Spring Boot applications, here’s the practical rule I finally settled on:

Use String for simple, fixed messages

Example

StringExample.JAVA JAVA
String appName = "PaymentService";

Use StringBuilder inside services or utilities for building content

Example

StringBuilderExample.JAVA JAVA
public String buildTraceLog(RequestData data) {
    StringBuilder sb = new StringBuilder();
    sb.append("RequestId: ").append(data.getId());
    sb.append(", User: ").append(data.getUser());
    sb.append(", Time: ").append(data.getTime());
    return sb.toString();
}

Use StringBuffer only when working with explicitly multi-threaded tasks

Running tasks across ExecutorService:

StringBufferExample.JAVA JAVA
StringBuffer buffer = new StringBuffer();
Runnable r = () -> buffer.append(Thread.currentThread().getName()).append(" ");
executor.submit(r);

Rarely used in actual Spring Boot business logic.

The Day After I Switched

After refactoring that API endpoint to use StringBuilder, the improvement was immediate:

  • check_circle CPU dropped
  • check_circle Latency improved by almost 30%
  • check_circle GC logs became clean
  • check_circle The on-call team stopped tagging me at 2 AM

That tiny detail choosing the right type, taught me that performance issues are often death by a thousand cuts. And most of those cuts come from using the wrong tool for the job.

Final Advice

If you’re building Spring Boot apps:

  • check_circle Reach for String when you want safety and clarity
  • check_circle Reach for StringBuilder when building dynamic text
  • check_circle Forget StringBuffer exists unless you're intentionally dealing with threads

Most developers I mentor struggle with this not because it’s hard but because nobody explains why it matters.

Performance problems rarely shout. They whisper and then explode during peak traffic.
Make the switch early. Your future self (and your production logs) will thank you.

READ BEYOND THE VOID

Technical blueprint background
Backend Notes

Update Id Sequence to Highest

My notes how to update Id Sequence in Posgresql...

Read Entry
Abstract digital network
Backend Notes

Difference between throw and throws and parent class of exceptions

I still remember the moment I realized I had been “half understanding” Java exception...

Read Entry
Circuit board macro
Backend Notes

Difference between AtomicLong and Long

When I was early in my Java career, I thought Long was enough for everything...

Read Entry