Home/Java/String Immutability in Java

String Immutability in Java

Understand why Strings cannot be changed after creation

💡 Think of a String like a word written with a permanent marker on paper. Once you write it, you can't erase or change it! If you want a different word, you have to get a new piece of paper and write the new word. This is what 'immutable' means - it cannot be changed after it's created!

🔒 What is Immutability?

Immutability means that once a String object is created, its content cannot be modified. Any operation that seems to change a String actually creates a new String object!

ImmutabilityDemo.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ImmutabilityDemo {
public static void main(String[] args) {
String original = "Hello";
System.out.println("Original: " + original);
// This looks like we're changing the string...
original.toUpperCase();
// But the original is UNCHANGED!
System.out.println("After toUpperCase(): " + original); // Still "Hello"
// To use the modified version, we must assign it
String modified = original.toUpperCase();
System.out.println("Modified: " + modified); // "HELLO"
System.out.println("Original still: " + original); // Still "Hello"
// Concatenation also creates NEW string
String greeting = "Hello";
greeting.concat(" World"); // Creates new string but we ignore it
System.out.println("After concat: " + greeting); // Still "Hello"
// Must assign to use the new string
greeting = greeting.concat(" World");
System.out.println("After assignment: " + greeting); // "Hello World"
}
}

🤔 Why Are Strings Immutable?

Java designers made Strings immutable for several important reasons:

1

Security

Strings are used for sensitive data like usernames, passwords, and file paths. If Strings were mutable, malicious code could change them after validation!

2

Thread Safety

Immutable objects are automatically thread-safe. Multiple threads can use the same String without worrying about changes

3

String Pool Optimization

Java reuses identical String literals to save memory. This only works because Strings can't change!

4

Hashcode Caching

Strings' hashcode can be cached and reused because it never changes, making HashMap operations faster

WhyImmutable.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class WhyImmutable {
public static void main(String[] args) {
// SECURITY EXAMPLE
String password = "secret123";
checkPassword(password);
// password is still "secret123" - it couldn't be changed!
// THREAD SAFETY EXAMPLE
String shared = "Thread Safe";
// Multiple threads can read this safely without synchronization
Thread t1 = new Thread(() -> System.out.println(shared));
Thread t2 = new Thread(() -> System.out.println(shared));
t1.start();
t2.start();
// STRING POOL EXAMPLE
String s1 = "Hello"; // Created in pool
String s2 = "Hello"; // Reuses same object from pool
System.out.println(s1 == s2); // true - same object!
// HASHCODE CACHING
String key = "myKey";
int hash1 = key.hashCode();
// Since String is immutable, hashCode is calculated once and cached
int hash2 = key.hashCode(); // Returns cached value - very fast!
System.out.println("Same hashcode: " + (hash1 == hash2));
}
static void checkPassword(String pwd) {
// Even if we wanted to change pwd here, we can't!
// This protects the original password string
if (pwd.equals("secret123")) {
System.out.println("Password correct");
}
}
}

✨ Benefits of Immutability

  • Safe to share across multiple parts of your program
  • Can be used as keys in HashMap and HashSet
  • Thread-safe by default - no synchronization needed
  • Predictable behavior - strings won't change unexpectedly
  • Memory efficient through String pooling
BenefitsDemo.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.HashMap;
public class BenefitsDemo {
public static void main(String[] args) {
// Benefit 1: Safe to share
String message = "Important Data";
processData(message);
System.out.println("Message unchanged: " + message);
// Benefit 2: Can be used as HashMap key
HashMap<String, Integer> scores = new HashMap<>();
String name = "Alice";
scores.put(name, 100);
// Even if we do operations on name, the key in HashMap is safe!
name = name.toUpperCase();
System.out.println("Score for 'Alice': " + scores.get("Alice")); // Still works!
// Benefit 3: String pooling saves memory
String a = "Java";
String b = "Java";
String c = "Java";
// All three reference the SAME object in memory!
System.out.println("Same object: " + (a == b && b == c));
}
static void processData(String data) {
// Can't accidentally modify the caller's string
data = data + " processed";
System.out.println("Inside method: " + data);
// Original string in main() is unchanged!
}
}

🔧 How to 'Modify' Strings

Since Strings are immutable, methods that seem to modify them actually return NEW strings:

ModifyingStrings.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ModifyingStrings {
public static void main(String[] args) {
// WRONG WAY - Ignoring return value
String text = "hello world";
text.toUpperCase(); // Creates new string but we ignore it!
System.out.println(text); // Still "hello world"
// RIGHT WAY - Assign the result
text = text.toUpperCase(); // Assign the new string
System.out.println(text); // Now "HELLO WORLD"
// Multiple modifications - each creates new object
String name = " john doe ";
name = name.trim(); // Remove whitespace
name = name.toUpperCase(); // Convert to uppercase
name = name.replace(" ", "_"); // Replace spaces
System.out.println(name); // "JOHN_DOE"
// Method chaining - more efficient
String name2 = " jane smith ";
name2 = name2.trim().toUpperCase().replace(" ", "_");
System.out.println(name2); // "JANE_SMITH"
// Concatenation creates new objects
String result = "Hello";
result = result + " "; // New object 1
result = result + "World"; // New object 2
System.out.println(result); // "Hello World"
}
}

💾 Memory Impact of Immutability

MemoryImpact.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MemoryImpact {
public static void main(String[] args) {
// BAD: Creates many temporary String objects
String bad = "";
for (int i = 0; i < 5; i++) {
bad = bad + i + " "; // Creates new String each iteration!
// Iteration 0: "0 "
// Iteration 1: "0 " + "1 " = "0 1 " (new object)
// Iteration 2: "0 1 " + "2 " = "0 1 2 " (new object)
// etc... Many temporary objects created!
}
System.out.println("Result: " + bad);
// GOOD: Use StringBuilder (we'll learn more about this later)
StringBuilder good = new StringBuilder();
for (int i = 0; i < 5; i++) {
good.append(i).append(" "); // Modifies same object
}
System.out.println("Result: " + good.toString());
// Visualizing object creation
String demo = "A";
System.out.println("Original object: " + System.identityHashCode(demo));
demo = demo + "B"; // New object created
System.out.println("After +B: " + System.identityHashCode(demo));
demo = demo + "C"; // Another new object
System.out.println("After +C: " + System.identityHashCode(demo));
// Notice: different hash codes = different objects!
}
}

🔄 String vs Mutable Objects

StringVsMutable.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.ArrayList;
public class StringVsMutable {
public static void main(String[] args) {
// IMMUTABLE - String
String immutable = "Hello";
modifyString(immutable);
System.out.println("After method: " + immutable); // Still "Hello"
// MUTABLE - ArrayList
ArrayList<String> mutable = new ArrayList<>();
mutable.add("Hello");
modifyList(mutable);
System.out.println("After method: " + mutable); // Changed to [Hello, World]!
// IMMUTABLE - new assignment doesn't affect reference
String s1 = "Original";
String s2 = s1;
s1 = "Modified";
System.out.println("s1: " + s1); // "Modified"
System.out.println("s2: " + s2); // Still "Original"
// MUTABLE - changes affect all references
ArrayList<String> list1 = new ArrayList<>();
list1.add("Original");
ArrayList<String> list2 = list1; // Same object reference
list1.add("Modified");
System.out.println("list1: " + list1); // [Original, Modified]
System.out.println("list2: " + list2); // [Original, Modified] - same!
}
static void modifyString(String s) {
s = s + " World"; // Creates new object, doesn't affect caller
}
static void modifyList(ArrayList<String> list) {
list.add("World"); // Modifies the actual object!
}
}

🔑 Key Concepts

New Object Created

Every string modification creates a new String object

String s = "Hi"; s = s + "!"; // new object created

Original Unchanged

The original String remains unchanged in memory

String s = "Hello"; s.toUpperCase(); // s is still "Hello"

Must Reassign

You must assign the result to use the modified string

String s = "hi"; s = s.toUpperCase(); // now s is "HI"

Memory Consideration

Many modifications create many objects; use StringBuilder for loops

Use StringBuilder when concatenating in loops

Best Practices

  • Use StringBuilder or StringBuffer for multiple modifications
  • Don't concatenate strings in loops - creates too many objects
  • Take advantage of String pooling by using literals when possible
  • Remember to assign the result of string methods to a variable
  • Use immutability to your advantage for thread-safe code

⚠️ Common Mistakes

  • Calling a method and ignoring the return value (str.trim() without assignment)
  • Concatenating strings in loops instead of using StringBuilder
  • Thinking that string methods modify the original string
  • Creating unnecessary String objects when StringBuilder would be better
  • Not understanding the memory implications of string concatenation

💼 Interview Tips

  • Be able to explain why Strings are immutable (security, thread safety, pooling)
  • Know that every string operation creates a new object
  • Understand the performance impact of string concatenation in loops
  • Know when to use String vs StringBuilder vs StringBuffer
  • Be able to explain String pooling and its relationship to immutability
  • Understand that final keyword on String class prevents inheritance, not immutability