Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 23

Python Strings: Immutability and Interning

Recommended resources

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.

This infographic highlights the five primary advantages of String immutability: Security, String Pool Optimization, Thread Safety, Fast Hashing, and Safe Class Loading.

Let's break down the most important reasons:

  1. 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 String after 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.

  2. Thread Safety: Since a String object can never be changed, it is inherently thread-safe. Multiple threads can access and share the same String instance 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.

  3. Performance (Hashing): Strings are the most common keys used in HashMaps and other hash-based collections. Because a String is immutable, its hash code is also immutable. The hashCode() method is computed the first time it's called and then cached within the String object itself. Subsequent calls to hashCode() 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 a HashMap would 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, s1 gets a reference to that existing object.
  • If not, a new String object for "hello" is created, placed in the pool, and s1 gets 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 someString object 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.

This diagram shows how different String variables reference objects in the String Pool or the Heap. `s1` and `s4` point to the same object in the pool. `s5`, created with `new`, is a separate object in the heap. `s2` is a new heap object from concatenation. `s3` points to a pooled object because `intern()` was called.

Let's trace the examples from the diagram:

  • String s1 = "hello"; and String 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 as s1.
  • 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");: The new keyword explicitly creates a new object on the heap, even though an identical string exists in the pool (from s4). Therefore, s4 == s5 is false.
  • 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 by s4) and returns its reference. Therefore, s3 == s4 is true.

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: String objects cannot be changed after creation. Methods like concat() produce new String objects.
  • 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.
  • literal vs. new: String s = "text"; uses the pool, while String 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.