Java’s String interning is a performance optimization that can save you a surprising amount of memory by ensuring that only one copy of any given string value exists in memory.

Let’s see it in action. Imagine you have a bunch of strings that are likely to be identical, perhaps read from a configuration file or a database.

String s1 = "hello";
String s2 = "hello";

System.out.println(s1 == s2); // true

Here, s1 and s2 point to the exact same String object in memory. The Java Virtual Machine (JVM) automatically interns string literals.

Now, consider strings created via new String().

String s3 = new String("world");
String s4 = new String("world");

System.out.println(s3 == s4); // false
System.out.println(s3.equals(s4)); // true

Even though s3 and s4 hold the same characters, they are distinct objects because new explicitly creates a new one. The JVM doesn’t automatically intern these.

This is where String.intern() comes in. It’s a method that allows you to manually intern a string. If the string’s value is already present in the "string pool" (a special heap area), intern() returns a reference to the existing string object. Otherwise, it adds the string to the pool and returns a reference to it.

String s5 = new String("java");
String s6 = new String("java");

System.out.println(s5 == s6); // false

String s7 = s5.intern();
String s8 = s6.intern();

System.out.println(s7 == s8); // true
System.out.println(s5.intern() == s6.intern()); // true

After calling intern(), s7 and s8 now refer to the same object in the string pool.

The core problem String.intern() solves is reducing memory overhead when you have many duplicate string values that are not string literals. If you’re reading, say, 10,000 user-uploaded tags, and many of them are "awesome" or "cool," creating 10,000 separate String objects for these common values is wasteful. Interning them ensures only one "awesome" and one "cool" object exist.

Internally, the JVM maintains a "string pool." When a string literal is encountered, the JVM checks if a string with that value already exists in the pool. If it does, the literal is made to reference the existing string. If not, a new string object is created, added to the pool, and the literal references it. The intern() method explicitly performs this check-and-add operation for any string object, not just literals.

The primary lever you control is when and how you call intern(). You call it on a String object, and it returns a reference to a string in the pool. You typically do this after creating a string object that might be a duplicate, like one read from user input or a file.

The common use case is to improve performance of == comparisons. Because == on objects compares references, if your strings are interned, s1 == s2 will be true if they have the same value, which is often much faster than s1.equals(s2). This is because equals() has to compare characters, whereas == is a simple pointer comparison.

The String.intern() method can cause OutOfMemoryError if you intern a very large number of unique strings, as the string pool is part of the heap. This is less common with modern JVMs that have more efficient string handling, but it’s still a possibility.

A subtle but important point is that the string pool is managed by the JVM, and its lifetime is tied to the JVM’s lifetime. When you call intern(), you’re essentially asking the JVM to give you a canonical representation of that string. If you’re dealing with a long-running application that continuously generates and interns new, unique strings, you can eventually exhaust the heap space dedicated to the string pool.

If you find yourself repeatedly interning strings that are never duplicates, you’re actually increasing memory usage by creating these new objects and then adding them to the pool. The JVM is smart about literals, so it’s usually best to rely on that for common, known values.

The next logical step after understanding interning is to explore how StringBuilder and StringBuffer are used for efficient string construction, especially within loops, and how their performance characteristics differ.

Want structured learning?

Take the full Java course →