Core Java: String in Java — String Pool, Immutability, StringBuilder & StringBuffer
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
booleanBut it is not.
String is a class provided by Java.
Its full name is:
java.lang.StringBecause 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 objectsJust 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 typec
↓
Reference variablenew 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" |
+---------+
↑
|
cIf 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:
trueWhy?
Because a and b can refer to the same object.
And:
System.out.println(a.equals(b));Output:
trueBecause 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:
falseBecause == compares the references, and the references point to different objects.
But:
System.out.println(a.equals(b));Output:
trueWhy?
Because equals() for String compares the contents.
Both contain:
helloSo 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
trueWhy?
a and b
String a = "hello";
String b = "hello";Both can refer to the same pooled String.
Therefore:
a == b → true
a.equals(b) → truec and d
String c = new String("hello");
String d = new String("hello");Two separate objects are created.
Therefore:
c == d → false
c.equals(d) → trueThis 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:
HelloYou might wonder:
Why didn’t
HellobecomeHello 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 WorldConceptually:
"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
HashSetImmutability 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 JavaHere 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 World12. 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 JavaSo 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
|
↓
StringBufferSuppose 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 operationIf 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 World18. 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 Synchronized19. 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 == bcan be:
trueand:
a.equals(b)is:
trueTwo new String() objects
String a = new String("hello");
String b = new String("hello");They are separate objects.
Therefore:
a == bis:
falseBut:
a.equals(b)is:
trueString 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 neededStringBuffer
Mutable
Synchronized
Useful when synchronized access to the same mutable buffer is requiredFinal 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 requiredSo 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. 🚀





