Sitemap

Core Java: String in Java — String Pool, Immutability, StringBuilder & StringBuffer

8 min readOct 6, 2026

--

We have already learned about primitive data types, arrays, classes, objects, and methods.

Now let’s talk about something we use almost every day while writing Java programs:

String

At first, String looks like a normal data type:

String name = "Aishu";

But here is something interesting:

String is a class. Yes, you heard it right! 😄

And one of the most important properties of String is:

String is immutable.

Let’s understand what this actually means and how Java stores Strings in memory.

1. String Is a Class

When we write:

String name = "Aishu";

String may look like a primitive data type such as:

int
double
char
boolean

But it is not.

String is a class provided by Java.

Its full name is:

java.lang.String

Because java.lang is automatically available in Java, we don't need to explicitly import String.

So conceptually:

String
↓
A class provided by Java
↓
We can create String objects

Just like we can create an object of our own class:

Calculator cal = new Calculator();

we can also create a String object.

2. Creating a String Using new

Let’s look at this:

String c = new String();

Here:

String
↓
Reference type
c
↓
Reference variable
new String()
↓
Creates a String object

The new keyword creates a new String object on the Heap.

Since we didn’t provide any value:

String c = new String();

the String contains an empty value:

""

However, there is an important difference when we pass a String literal.

Consider:

String c = new String("hello");

Here, "hello" is a String literal, so Java uses the String Pool for that literal.

At the same time, new String("hello") explicitly creates another String object on the Heap.

Conceptually:

JVM Memory
        String Pool             Heap
----------- ----
| "hello" | | "hello" |
----------- ---------
↑ ↑
| |
literal new String()

So we need to remember:

new String("hello") can involve a pooled "hello" String and a separate heap String object.

3. String Literal

Now let’s look at the more common way:

String c = "hello";

Here we are using a String literal.

Java maintains a special pool for String literals called the:

String Pool

So:

String c = "hello";

can be visualized as:

String Pool
+---------+
| "hello" |
+---------+
↑
|
c

If the "hello" literal is already present in the pool, Java can reuse the existing String object.

This is one of the reasons Java doesn’t need to create a separate String object every time we write the same literal.

4. The Interesting Part — Two References With the Same Value

Now let’s compare these two cases.

Case 1: String literals

String a = "hello";
String b = "hello";

Both use the String Pool.

If "hello" is already in the pool, both references can point to the same pooled object.

Conceptually:

String Pool
             +---------+
a ---------->| "hello" |<---------- b
+---------+

So:

System.out.println(a == b);

Output:

true

Why?

Because a and b can refer to the same object.

And:

System.out.println(a.equals(b));

Output:

true

Because their contents are also the same.

5. What Happens With new String()?

Now let’s do this:

String a = new String("hello");
String b = new String("hello");

This time, we are explicitly asking Java to create a new String object for each new String("hello").

Conceptually:

String Pool                 Heap
+---------+              +---------+
| "hello" | | "hello" |
+---------+ +---------+
↑
object 1
+---------+
| "hello" |
+---------+
↑
object 2

So a and b refer to two different heap objects.

Now:

System.out.println(a == b);

Output:

false

Because == compares the references, and the references point to different objects.

But:

System.out.println(a.equals(b));

Output:

true

Why?

Because equals() for String compares the contents.

Both contain:

hello

So remember this very important rule:

==
→ compares references
.equals()→ compares String content

6. One Example That Makes Everything Clear

Look at this:

String a = "hello";
String b = "hello";
String c = new String("hello");
String d = new String("hello");
System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(c == d);
System.out.println(c.equals(d));

Output:

true
true
false
true

Why?

a and b

String a = "hello";
String b = "hello";

Both can refer to the same pooled String.

Therefore:

a == b       → true
a.equals(b) → true

c and d

String c = new String("hello");
String d = new String("hello");

Two separate objects are created.

Therefore:

c == d       → false
c.equals(d) → true

This is one of the most important String concepts to understand.

7. String Is Immutable

Now let’s come to the second major concept.

String is immutable.

What does immutable mean?

It means:

Once a String object is created, its contents cannot be changed.

Let’s see an example.

String name = "Aishu";

Now suppose we write:

name = "Rahul";

Did the "Aishu" String object change into "Rahul"?

No.

The reference variable name simply starts referring to another String.

Conceptually:

Before:
name
↓
"Aishu"
After:name
↓
"Rahul"

The original "Aishu" object itself was not modified.

8. String Concatenation and Immutability

Consider:

String message = "Hello";
message.concat(" World");System.out.println(message);

Output:

Hello

You might wonder:

Why didn’t Hello become Hello World?

Because String is immutable.

The concat() operation creates a new String value.

If we want to keep that new value, we have to assign it:

String message = "Hello";
message = message.concat(" World");System.out.println(message);

Now the output is:

Hello World

Conceptually:

"Hello"
|
| concat(" World")
↓
"Hello World"

The original "Hello" String wasn't modified.

9. Why Did Java Make String Immutable?

There are several important reasons.

1. String Pool

Remember:

String a = "hello";
String b = "hello";

Both can share the same String object.

Get Aishu Reddy’s stories in your inbox

Join Medium for free to get updates from this writer.

If Strings were mutable, changing the shared object could unexpectedly affect other references.

Because Strings are immutable, sharing them is safe.

2. Security

Strings are used for important information such as:

  • URLs
  • File paths
  • Class names
  • Configuration values
  • Authentication-related information

Making Strings immutable prevents their contents from being unexpectedly changed.

3. Thread Safety

Because a String cannot be modified after it is created, multiple threads can safely read the same String object.

4. Hashing

Strings are commonly used as keys in:

HashMap
HashSet

Immutability helps keep their hash-related behavior stable while they are being used as keys.

10. Why Do We Need StringBuilder?

Now think about this code:

String result = "";
for (int i = 0; i < 1000; i++) {
result = result + i;
}

Remember:

String is immutable.

Every time we concatenate something, a new String value may be created.

If we perform many modifications, this can create many temporary String objects.

This is where StringBuilder becomes useful.

11. StringBuilder

StringBuilder is a mutable sequence of characters.

Mutable means:

Its contents can be changed.

Example:

StringBuilder builder = new StringBuilder();
builder.append("Hello");
builder.append(" ");
builder.append("Java");
System.out.println(builder);

Output:

Hello Java

Here we are modifying the same StringBuilder object.

Some commonly used methods are:

append()
insert()
delete()
reverse()

For example:

StringBuilder builder = new StringBuilder("Hello");
builder.append(" World");System.out.println(builder);

Output:

Hello World

12. StringBuffer

Now we have another class:

StringBuffer

StringBuffer is also a mutable sequence of characters, just like StringBuilder.

For example:

StringBuffer buffer = new StringBuffer();
buffer.append("Hello");
buffer.append(" Java");
System.out.println(buffer);

Output:

Hello Java

So you might ask:

If StringBuilder and StringBuffer both allow modification, what is the difference?

The main difference is:

StringBuffer is synchronized, while StringBuilder is not.

13. What Does Thread-Safe Mean?

This is where things get interesting.

Imagine two threads:

Thread 1
|
↓
StringBuffer
Thread 2
|
↓
StringBuffer

Suppose both threads are trying to modify the same StringBuffer object at the same time.

StringBuffer’s methods are synchronized.

A simplified way to think about it is:

Thread 1 → gets the lock → performs operation
↓
releases lock
↓
Thread 2 → gets the lock → performs operation

If Thread 1 is currently executing a synchronized operation on that StringBuffer object, Thread 2 may have to wait until the lock becomes available.

This prevents multiple threads from simultaneously entering the synchronized operation on the same object in a way that could cause unsafe access.

That’s what we mean when we say:

StringBuffer is thread-safe.

14. StringBuilder Is Not Synchronized

StringBuilder does not provide the same synchronization.

Imagine:

Thread 1 ───────→ StringBuilder
↑
Thread 2 ───────→ |

There is no built-in synchronization protecting the same StringBuilder object from concurrent access.

So if multiple threads modify the same StringBuilder without external synchronization, we can run into concurrency problems.

This is why StringBuilder is generally preferred when:

  • We are working in a single-threaded context
  • We don’t need synchronization
  • We want less synchronization overhead

15. StringBuilder vs StringBuffer

The easiest way to remember:

String
↓
Immutable
StringBuilder
↓
Mutable
↓
Not synchronized
StringBuffer
↓
Mutable
↓
Synchronized

16. Which One Should We Use?

Use String

When you have normal text:

String name = "Aishu";

You don’t need to modify the same String repeatedly.

Use StringBuilder

When you are repeatedly building or modifying text:

StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" Java");
sb.append(" World");

This is generally the preferred choice when synchronization isn’t required.

Use StringBuffer

When you need a mutable character sequence and synchronized access to the same object is required:

StringBuffer buffer = new StringBuffer();
buffer.append("Hello");
buffer.append(" Java");

17. One Complete Example

Let’s put all three together:

public class Main {
    public static void main(String[] args) {        // String
String name = "Aishu";
// StringBuilder
StringBuilder builder = new StringBuilder("Hello");
builder.append(" Java");
// StringBuffer
StringBuffer buffer = new StringBuffer("Hello");
buffer.append(" World");
System.out.println(name);
System.out.println(builder);
System.out.println(buffer);
}
}

Output:

Aishu
Hello Java
Hello World

18. The Complete Picture

Now we can connect everything together.

STRING
|
Class
|
-------------------------
| |
String Literal new String()
| |
String Pool Heap
| |
↓ ↓
Can be shared New object
|
↓
Immutable
|
↓
Cannot modify existing
String object
|
↓
Need frequent changes?
|
-------------------
| |
↓ ↓
StringBuilder StringBuffer
| |
Mutable Mutable
No sync Synchronized

19. Quick Revision

Let’s remember the most important points.

String is a class

String name = "Aishu";

String is not a primitive data type.

new String("hello")

String a = new String("hello");

The "hello" literal can exist in the String Pool, while new String("hello") creates a separate String object on the Heap.String literal

String a = "hello";
String b = "hello";

Both can refer to the same pooled String object.

Therefore:

a == b

can be:

true

and:

a.equals(b)

is:

true

Two new String() objects

String a = new String("hello");
String b = new String("hello");

They are separate objects.

Therefore:

a == b

is:

false

But:

a.equals(b)

is:

true

String is immutable

Once a String object is created, its contents cannot be changed.

StringBuilder

Mutable
Not synchronized
Generally preferred for frequent modifications when synchronization isn't needed

StringBuffer

Mutable
Synchronized
Useful when synchronized access to the same mutable buffer is required

Final Thought

At first, String looks like a simple data type:

String name = "Aishu";

But now we know there is much more happening behind the scenes.

We learned that:

String is a class, not a primitive data type.

We also learned about the String Pool, where String literals can be shared.

We learned that:

String is immutable, meaning its existing contents cannot be changed.

And when we need to modify text repeatedly, Java gives us two mutable alternatives:

StringBuilder
↓
Mutable
Not synchronized
Generally preferred when synchronization isn't needed
StringBuffer
↓
Mutable
Synchronized
Useful when synchronized access is required

So the simple rule to remember is:

String for normal text, StringBuilder for frequent modifications, and StringBuffer when synchronized access to a mutable buffer is needed.

And that is the interesting story behind something we use every day in Java:

String. 🚀

--

--

Aishu Reddy
Aishu Reddy

Written by Aishu Reddy

Turning ideas into code and bugs into lessons. 💻✨