Java String Fundamentals & String Constant Pool (SCP)
What is a String? ยท java.lang.String ยท String Literals vs new String() ยท String Constant Pool (SCP) ยท String Interning (intern()) ยท String Immutability Deep Dive ยท Compact Strings (byte[] in Java 9+) ยท Indexing & charAt()
Comprehensive deep dive into Java Strings: understanding the java.lang.String class, the internal difference between String literals and heap objects, the String Constant Pool (SCP) memory region, why strings are immutable in Java, compact string architecture in modern JVMs, and fundamental 0-based character indexing.
1. What is a String in Java?
In computer programming, text is the primary medium through which humans interact with software. In Java, textual data is represented by the java.lang.String class.
Unlike primitive data types (int, double, char, boolean) which store raw binary bits directly in Stack memory, a String is a Reference Type (Object).
A String object represents an immutable, ordered sequence of Unicode characters.
Primitive Type:
[ int age = 21 ] ===> Stack Memory holds literal value: 21
Reference Type:
[ String name = "Ravi" ] ===> Stack holds Reference Address: 0x4F12
|
v
Heap / SCP Memory: ['R', 'a', 'v', 'i']
Because strings are used in virtually every line of enterprise Java code (database queries, JSON payloads, HTTP headers, authentication tokens), Java provides first-class language support for strings, allowing you to create them using double quotes without explicitly calling new.
2. Creating Strings: String Literals vs new String()
There are two primary ways to create a string object in Java:
1. Using a String Literal:
String s1 = "Java";
String s2 = "Java";When you create a string literal, the JVM checks the String Constant Pool (SCP) located inside the Heap memory:
- If
"Java" already exists in the SCP, no new object is created. The JVM simply returns a reference to the existing instance.- Both
s1 and s2 point to the exact same memory location (s1 == s2 is true).
2. Using the new Keyword:
String s3 = new String("Java");When you use
new String("Java"):- The JVM forces the creation of a new, distinct Object in the general Heap memory, outside the SCP.
- It also ensures
"Java" exists in the SCP.- Therefore,
s1 == s3 evaluates to false because they reside at different memory addresses!
HEAP MEMORY
+-------------------------------------------------------+
| |
| General Heap Objects: |
| +------------------------+ |
| | String Object (s3) | (Address: 0x7B20) |
| | Value: "Java" | |
| +------------------------+ |
| |
| +-----------------------------------------------+ |
| | STRING CONSTANT POOL (SCP) | |
| | | |
| | +-----------------------+ | |
| | | "Java" (s1, s2) | (Address: 0x1A05) | |
| | +-----------------------+ | |
| | | |
| +-----------------------------------------------+ |
+-------------------------------------------------------+3. String Interning: The intern() Method
If you have a heap string object created via new String() or dynamic concatenation and you want to point to the canonical SCP instance to save memory, you can call the intern() method:
String heapStr = new String("Java");
String poolStr = heapStr.intern(); // Returns SCP reference
System.out.println(heapStr == "Java"); // false (Heap vs SCP)
System.out.println(poolStr == "Java"); // true (Both are in SCP)
Why Interning Matters in Industry:
When reading millions of duplicate records from large CSVs or JSON files (e.g. state names like "California", country codes like "USA"), interning duplicate strings allows millions of references to share a single SCP object, drastically reducing Heap memory consumption.
4. Why are Strings Immutable in Java? (4 Core Reasons)
Once a String object is created in Java, its character sequence can NEVER be modified. Any method that appears to modify a string (like toUpperCase(), concat(), replace()) actually allocates and returns a brand-new String object in memory.
String str = "Hello";
str.concat(" World"); // Modifies nothing on str!
System.out.println(str); // Still prints "Hello"
str = str.concat(" World"); // Explicitly reassigning reference to the new object
System.out.println(str); // Prints "Hello World"
Why did Java designers make String immutable?
1. String Constant Pool (SCP) Sharing: If strings were mutable, changing the value through reference s1 would silently corrupt the value for s2 and all other threads sharing that pool object!
2. Security: Strings are used for database connection URLs, usernames, passwords, file paths, and network sockets. If a string were mutable, an attacker could pass a valid file path to a verification method and mutate it to access /etc/passwd before the file is opened (Time-of-Check to Time-of-Use vulnerability).
3. Thread Safety: Because string objects cannot be changed by any thread, multiple concurrent threads can share strings without synchronization locks, completely eliminating race conditions.
4. HashCode Caching: Because the content is fixed forever, Java calculates the hashCode of a string only once on first use and caches it in a private field hash. This makes String exceptionally fast when used as keys in HashMap and HashSet.
5. Compact Strings Architecture (Java 9+)
Historically in Java 8 and earlier, strings were internally stored as an array of 16-bit characters: char[] value. Because most enterprise strings contain alphanumeric ASCII characters (requiring only 8 bits / 1 byte), half of the memory was wasted with zero-padding bytes.
Starting in Java 9 LTS, Java introduced Compact Strings (JEP 254):
// Internal representation in java.lang.String (Java 9+):
private final byte[] value;
private final byte coder; // 0 for LATIN1 (1 byte/char), 1 for UTF-16 (2 bytes/char)- If the string contains only Latin-1 characters (English letters, numbers, common symbols), the JVM encodes it as 1 byte per character.
- If any character requires Unicode (such as emojis or Telugu/Japanese scripts), the coder switches to UTF-16 (2 bytes per character).
This automatic internal optimization reduced overall JVM heap footprint by 15% to 30% across real-world enterprise applications with zero code changes!
6. String Indexing, length(), and charAt()
Strings in Java use 0-based indexing. The first character is located at index 0, and the last character is at index length() - 1.
String: "J a v a"
Index: 0 1 2 3 (length = 4)- length(): Returns the total number of characters in the string. (Notice the parentheses (), unlike arrays which use the property .length).
- charAt(int index): Returns the char at the specified index.
- Bounds Rule: If you provide an index < 0 or >= length(), Java throws a StringIndexOutOfBoundsException at runtime.
Beginner Example & Code Anatomy
public class Main {
public static void main(String[] args) {
System.out.println("=== 1. String Literals vs new String() ===");
String s1 = "Java";
String s2 = "Java";
String s3 = new String("Java");
String s4 = s3.intern(); // Get reference from SCP
// Reference equality checks (Address comparison)
System.out.println("s1 == s2 (Both in SCP) : " + (s1 == s2)); // true
System.out.println("s1 == s3 (SCP vs Heap) : " + (s1 == s3)); // false
System.out.println("s1 == s4 (SCP vs intern()) : " + (s1 == s4)); // true
// Content equality checks
System.out.println("s1.equals(s3) (Content) : " + s1.equals(s3)); // true
System.out.println("
=== 2. Proof of String Immutability ===");
String original = "Hello";
original.concat(" World"); // Return value discarded!
System.out.println("Original after concat() : " + original); // "Hello"
original = original.concat(" World"); // Explicit reassignment
System.out.println("Original after reassignment : " + original); // "Hello World"
System.out.println("
=== 3. String Indexing & Traversal ===");
String greeting = "Java 21";
System.out.println("String text : "" + greeting + """);
System.out.println("Total Length : " + greeting.length());
System.out.println("Character at index 0 : " + greeting.charAt(0));
System.out.println("Character at index 5 : " + greeting.charAt(5));
System.out.println("Last Character : " + greeting.charAt(greeting.length() - 1));
System.out.print("Iterating characters via loop: ");
for (int i = 0; i < greeting.length(); i++) {
System.out.print("[" + i + "]=" + greeting.charAt(i) + " ");
}
System.out.println();
}
}
๐ Line-by-Line Code Explanation
String s1 = "Java"; String s2 = "Java";
Creates a single "Java" literal in the String Constant Pool (SCP). Both s1 and s2 point to the identical memory address.
String s3 = new String("Java");
Explicitly allocates a new distinct String object in Heap memory, separate from the SCP pool.
String s4 = s3.intern();
Calls intern() to retrieve the canonical pooled string from the SCP, making s4 point to the exact same reference as s1.
original.concat(" World");
Demonstrates immutability: concat() generates a new string "Hello World" in memory, but leaves the "original" variable pointing to "Hello".
greeting.charAt(i);
Retrieves the character at index i (0 to length - 1) in constant O(1) time.
Practical Real-World Example
public class PracticalApplication {
public static void main(String[] args) {
// Industry Simulation: High-throughput memory deduplication via interning
String rawCategory1 = new String("ELECTRONICS");
String rawCategory2 = new String("ELECTRONICS");
String rawCategory3 = new String("ELECTRONICS");
// Without interning: 3 distinct heap objects consuming unnecessary RAM
System.out.println("Before Interning:");
System.out.println("Obj 1 == Obj 2 : " + (rawCategory1 == rawCategory2)); // false
// With interning: all references point to single pooled constant
String pooled1 = rawCategory1.intern();
String pooled2 = rawCategory2.intern();
String pooled3 = rawCategory3.intern();
System.out.println("
After Interning (Memory Deduplication):");
System.out.println("Pooled 1 == Pooled 2 : " + (pooled1 == pooled2)); // true
System.out.println("Pooled 2 == Pooled 3 : " + (pooled2 == pooled3)); // true
System.out.println("Canonical Category : " + pooled1);
}
}
- Using == to check if two strings have the same text content. == checks memory addresses; always use .equals().
- Forgetting that strings are immutable and writing str.toUpperCase(); without assigning the result back (str = str.toUpperCase();).
- Accessing str.charAt(str.length()) instead of str.charAt(str.length() - 1), causing StringIndexOutOfBoundsException.
- Confusing String.length() (a method with parentheses) with Array.length (a property without parentheses).
Test your understanding by writing the code directly in your editor or running in our online Java compiler:
// Coding Challenge:
// Given a string "DEVELOPER", write a program to:
// 1. Print the first character and the last character.
// 2. Print the character at the exact middle index.
// 3. Print the string in reverse order using a for loop and charAt().
public class Challenge {
public static void main(String[] args) {
String word = "DEVELOPER";
System.out.println("First: " + word.charAt(0));
System.out.println("Last: " + word.charAt(word.length() - 1));
System.out.println("Middle: " + word.charAt(word.length() / 2));
System.out.print("Reversed: ");
for (int i = word.length() - 1; i >= 0; i--) {
System.out.print(word.charAt(i));
}
System.out.println();
}
}
๐ก Frequently Asked Questions & Interview Insights
โ Where is the String Constant Pool (SCP) located in modern JVMs?
Prior to Java 7, the SCP was located in the PermGen space. Since Java 7 and continuing in Java 8-21+, the SCP is located inside the main Heap Memory, allowing it to be garbage collected when strings are no longer referenced.
โ Why does String have a private final byte[] value instead of char[] in Java 9+?
To implement Compact Strings (JEP 254). Most enterprise strings contain only Latin-1 characters which require only 1 byte (8 bits) per character instead of 2 bytes (16 bits) in UTF-16, cutting overall string memory usage by nearly 50%.
โ How many objects are created by String s = new String("Java");?
Two objects: One object is created in the String Constant Pool (SCP) for the literal "Java" (if not already present), and one new object is created in the general Heap memory referenced by s.
๐ Quick Chapter Recap
- A String in Java is an immutable reference type representing a sequence of Unicode characters.
- String literals are automatically cached in the String Constant Pool (SCP) inside Heap memory.
- new String("text") forces the creation of a distinct heap object outside the pool.
- Immutability ensures thread safety, security, SCP reusability, and fast cached hashCode() lookups.
- Compact Strings in Java 9+ use byte[] with a coder byte to dynamically save 50% memory on Latin-1 text.
- Strings are 0-indexed; use length() for character count and charAt(index) for character retrieval.