Welcome to our next lesson. In our previous discussion, we explored the mechanics of autoboxing and unboxing, and specifically how the Integer cache creates surprising behaviors when comparing wrapper objects with ==. You learned that the JVM reuses objects for common values as an optimization.
Today, we'll shift our focus to one of the most fundamental and frequently used classes in Java: String. We will investigate its most defining characteristic—immutability. You'll learn what it means for a String to be immutable, the critical design reasons behind this choice, and how the JVM employs a powerful memory-saving mechanism called the String Constant Pool. This lesson will also clarify the role of the intern() method, a tool for interacting directly with this pool.
The Core Concept: String Immutability
At its heart, immutability means that once a String object is created, its state—the sequence of characters it represents—cannot be changed. Any method that appears to modify a string, such as concat(), substring(), or toUpperCase(), does not alter the original object. Instead, it creates and returns a new String object containing the result.
This is a frequent point of confusion. A developer might write myString.toUpperCase(); and expect myString to now be in uppercase, but the original string remains untouched. The video below clearly demonstrates this concept and clarifies how reference variables work in this context.
Top String Interview Questions in Java You Must Prepare
This segment from "The Curious Coder" provides a clear, practical demonstration of string immutability in action.
Please watch these two clips: First, watch the section from the explanation of concatenation. Notice how s1.concat(" world") creates a new "hello world" object in memory, but the s1 reference doesn't automatically point to it. The reference must be explicitly reassigned. Then, jump to the example using toUpperCase() from this part of the video. It reinforces the same principle: the method returns a new string, and the original remains unchanged unless you reassign the variable.
This immutability isn't just a convention; it's structurally enforced by the String class itself.
The Secret Life of a Java String | belief driven design
This article by "belief driven design" delves into the internal structure of the String class, explaining how immutability is enforced.
Please read the section titled Immutability as Structure. Pay close attention to how the use of private final fields and declaring the String class as final work together to guarantee that a String's content can never be mutated after creation.
The Rationale for Immutability
Making String immutable was a deliberate and crucial design decision in Java. It provides several powerful benefits related to security, concurrency, and performance.

Let's break down the most important reasons:
-
Security: Strings are often used to hold sensitive information like file paths, network connection URLs, and class names loaded via reflection. If strings were mutable, a malicious piece of code could potentially change the value of a
Stringafter it has been validated. For example, a method could check a file path for security, but another thread could then modify the path to point to a sensitive system file before the I/O operation occurs. Immutability prevents this entire class of vulnerabilities. -
Thread Safety: Since a
Stringobject can never be changed, it is inherently thread-safe. Multiple threads can access and share the sameStringinstance without any need for synchronization or locks. This simplifies concurrent programming significantly, as you don't have to worry about one thread corrupting a string that another thread is reading. -
Performance (Hashing): Strings are the most common keys used in
HashMaps and other hash-based collections. Because aStringis immutable, its hash code is also immutable. ThehashCode()method is computed the first time it's called and then cached within theStringobject itself. Subsequent calls tohashCode()simply return the cached value. If strings were mutable, the hash code would have to be recalculated every time, and changing a string's value while it's a key in aHashMapwould break the map's internal structure.
These points are summarized well in the following article.
Java Strings Deep Dive (2026) | tutorialQ
This article from tutorialQ provides a concise summary of the reasons behind String immutability.
Read the first section, String Immutability — Why and How. This provides clear, bulleted explanations for thread safety, hash-code caching, and security.
The String Pool and the intern() Method
The immutability of strings enables a significant memory optimization: the String Constant Pool. This is a special area in the heap where the JVM stores a single, canonical instance of each string literal that appears in your code.
When you write String s1 = "hello";, the JVM checks the pool.
- If "hello" is already in the pool,
s1gets a reference to that existing object. - If not, a new
Stringobject for "hello" is created, placed in the pool, ands1gets a reference to it.
This means if you declare String s2 = "hello"; elsewhere in your code, both s1 and s2 will point to the exact same object.
However, when you use the new keyword, you explicitly bypass the pool and force the creation of a new object on the heap. This is where the == vs. .equals() distinction becomes critical, just as it was for Integer objects.
The intern() method is your bridge to the String Pool for strings that were not created as literals. When you call someString.intern(), the JVM checks the pool for a string with the same content.
- If an equal string is found in the pool,
intern()returns a reference to the pooled string. - If not, the
someStringobject is added to the pool, and a reference to it is returned.
This diagram perfectly illustrates the relationships between string literals, new String(), the intern() method, the String Pool, and heap memory.

Let's trace the examples from the diagram:
String s1 = "hello";andString s4 = "hello world";: These are literals, so they are placed in the String Pool. If another literal"hello"appeared, it would point to the same object ass1.String s2 = s1.concat(" World");: Concatenation creates a new object,s2, on the heap. It is not automatically placed in the pool.String s5 = new String("hello World");: Thenewkeyword explicitly creates a new object on the heap, even though an identical string exists in the pool (froms4). Therefore,s4 == s5isfalse.String s3 = s1.concat(" World").intern();: Here, a new string "hello World" is created. Then,intern()is called. The JVM finds the equivalent string in the pool (the one referenced bys4) and returns its reference. Therefore,s3 == s4istrue.
For a deeper dive into the mechanics of the String Pool and intern(), the following reading is excellent. It also touches on modern JVM optimizations like G1 String Deduplication, which is relevant given your interest in JVM internals.
The Secret Life of a Java String | belief driven design
This article continues its deep dive into the String Pool, interning, and their practical consequences.
Please read the section The String Pool and Interning. Focus on understanding: How the pool automatically handles literals. The classic == trap and why .equals() is mandatory for content comparison. The purpose of intern() for de-duplicating strings created at runtime. The modern context: why manual intern() is less necessary now due to G1's String Deduplication feature.
Conclusion
In this lesson, we've unpacked the concept of String immutability and its far-reaching consequences in Java. Understanding this is not just academic; it's key to writing secure, efficient, and correct Java code.
Key Takeaways:
- Immutable by Design:
Stringobjects cannot be changed after creation. Methods likeconcat()produce newStringobjects. - Critical Rationale: Immutability is essential for security (preventing tampering), thread safety (allowing safe sharing), and performance (enabling hash code caching).
- String Constant Pool: The JVM saves memory by storing a single copy of each string literal in this pool.
literalvs.new:String s = "text";uses the pool, whileString s = new String("text");always creates a new object on the heap.intern()Method: This method provides a way to find or add a string to the pool at runtime, returning the canonical reference.- Always Use
.equals(): For comparing string content,.equals()is the correct and reliable method. Use==only when you intentionally need to check if two references point to the same object.
In our next lesson, we will explore the mutable counterparts to String: StringBuilder and StringBuffer. You'll learn why they exist, how they work internally, and when you should use them instead of string concatenation.