Strings in Java
Introduction
In Java, a String is an object that represents a sequence of characters. It is one of the most heavily used classes in the Java programming language. Understanding how Strings work internally—specifically their immutability, memory management, and performance implications—is crucial for writing efficient Java applications.
The Core Concept: String Immutability
The most defining characteristic of the String class in Java is that it is immutable. Once a String object is created, its internal state (the sequence of characters) cannot be changed.
Why are Strings Immutable?
- Security: Strings are widely used as parameters for network connections, database URLs, and file paths. If a string were mutable, its value could be maliciously altered after security checks have passed.
- Thread Safety: Because they cannot be modified,
Stringinstances are inherently thread-safe and can be shared freely among concurrent threads without synchronization. - Caching and Memory Optimization: Immutability allows Java to cache string literals in a special memory region called the String Pool.
What happens when you "modify" a String?
When you perform operations that appear to modify a string, such as concatenation or replacement, the JVM actually creates an entirely new String object.
String s = "Hello";
s.concat(" World"); // Creates a new String "Hello World" but it is not assigned.
System.out.println(s); // Prints "Hello"
s = s + " World"; // Creates "Hello World" and assigns the reference back to 's'.
The String Pool
The JVM maintains a special area in the heap memory called the String Constant Pool.
When you create a string using a string literal ("..."), the JVM checks the String Pool first:
- If an identical string already exists in the pool, a reference to the pooled instance is returned.
- If it does not exist, a new
Stringobject is placed in the pool, and its reference is returned.
Literal vs. new Keyword
// 1. Literal creation (Uses String Pool)
String s1 = "Java";
String s2 = "Java";
System.out.println(s1 == s2); // true - they point to the exact same object in memory
// 2. Using 'new' keyword (Forces heap allocation outside the pool)
String s3 = new String("Java");
System.out.println(s1 == s3); // false - s3 is a distinct object in the heap
// 3. Interning
String s4 = s3.intern(); // Puts s3 into the pool (or gets the existing pool reference)
System.out.println(s1 == s4); // true
Note: Always use .equals() to compare the value of strings, not == which compares memory references.
Memory Architecture Under the Hood
Prior to Java 9, the String class stored characters internally in a char[] array (which uses 2 bytes per character for UTF-16).
Compact Strings (Java 9+):
To optimize memory, Java 9 introduced "Compact Strings". The internal representation was changed to a byte[] array combined with an encodingFlag.
- If a string contains only ISO-8859-1 (Latin-1) characters, it uses 1 byte per character.
- If it contains characters requiring more space (like emojis or non-Latin scripts), it gracefully degrades to UTF-16 (2 bytes per character).
This drastically reduces the memory footprint of Strings in modern Java applications.
Mutable Alternatives: StringBuilder and StringBuffer
Because concatenating Strings in a loop creates many temporary objects (putting pressure on the Garbage Collector), Java provides mutable alternatives.
StringBuilder
- Use case: When building or modifying strings repeatedly in a single-threaded environment.
- Properties: Mutable, fast, not thread-safe.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i).append(",");
}
String result = sb.toString();
StringBuffer
- Use case: When modifying strings across multiple threads.
- Properties: Mutable, slower, thread-safe (methods are synchronized).
Summary Best Practices
- Avoid
new String("..."): Always use string literals to leverage the String Pool. - Use
.equals()for comparison: Never use==unless you specifically want to check if two references point to the exact same memory location. - Use
StringBuilderin loops: Never use the+operator to concatenate strings inside a loop. - Be cautious with
.intern(): While it can save memory, excessive interning can bloat the String Pool, leading to performance degradation.