Back to Blog
Java

Java intern method: How String Interning Works

java intern method: Learn how Java's String.intern() method works, its effect on memory and performance, and when to use it safely in production code.

String interningJava String poolJVM memoryJava performanceString equality
Diagram showing Java string pool and intern method referencing shared string instances

The java intern methodString.intern() — is a lesser-known but powerful tool for controlling how strings are stored in the JVM. When you call intern() on a String, the JVM returns a canonical representation from the string pool. If an equal string already exists in the pool, that existing instance is returned; otherwise, the current string is added to the pool and returned. This behavior directly affects memory usage, reference equality, and application performance, but it is not always the right choice.

What String.intern() Actually Does

The intern() method is defined on java.lang.String. It returns a string that is guaranteed to be the same object as any other string with the same content that has been interned. The JVM maintains a pool of interned strings. For example:

String a = new String("hello"); String b = new String("hello"); String c = a.intern(); String d = b.intern(); System.out.println(c == d); // true System.out.println(a == b); // false

Here, a and b are distinct objects on the heap. After calling intern(), c and d both point to the same canonical instance from the pool. The == comparison works because both references point to the same object.

String literals are automatically interned. When you write "hello" in source code, the compiler places it in the constant pool, and at runtime the JVM ensures that identical literals share the same instance. The intern() method extends this behavior to strings created at runtime.

How the String Pool Works

In modern JVMs, the string pool is a hash table that lives in the heap, specifically in a region that is part of the old generation or metaspace depending on the JVM version. Historically, interned strings were stored in the permanent generation (PermGen), which could cause OutOfMemoryError if too many strings were interned. Since Java 7, the pool is in the main heap, and since Java 8, PermGen was replaced by metaspace, which uses native memory. The exact location and sizing depend on the JVM implementation and flags like -XX:StringTableSize.

The pool is not a simple collection; it is a hash table with a fixed number of buckets. When you call intern(), the JVM computes a hash of the string content, looks up the bucket, and either finds an existing entry or inserts a new one. This lookup is O(1) on average but can degrade if many strings collide.

When to Use intern() in Practice

The primary use case for intern() is to reduce memory when the same string value appears many times. For example, consider a system that reads a large number of records with a limited set of status codes like "SUCCESS", "FAILURE", "PENDING". Without interning, each record might create a new String object with the same content, consuming heap space. Interning ensures that all references to "SUCCESS" point to the same object, drastically reducing memory footprint.

Another common scenario is using interned strings as keys in a map or for synchronization. Because interned strings can be compared with ==, you can use them as lightweight constants. However, this only works if you control the set of strings and ensure they are all interned.

Map<String, Integer> statusCounts = new HashMap<>(); String status = readStatusFromInput(); // returns a new String status = status.intern(); // canonicalize statusCounts.merge(status, 1, Integer::sum);

In this example, interning the status before using it as a map key avoids storing duplicate string objects for the same status value.

Memory and Performance Tradeoffs

Interning is not free. The first time a string is interned, the JVM must insert it into the pool, which involves hashing and potential resizing of the pool. Subsequent calls to intern() for the same content require a hash lookup. For a small number of unique strings, the overhead is negligible. But when you intern a large number of distinct strings, the pool grows, and the cost of insertion and lookup increases.

The memory benefit is real only when there is a high degree of duplication. If every string is unique, interning adds overhead without saving memory. Worse, it can cause a memory leak if you intern strings that are not truly unique or that come from an unbounded input. The pool holds strong references to interned strings, so they cannot be garbage collected as long as the pool exists. This is a common pitfall: interning user-generated data can fill the pool with values that are never reused.

Another performance consideration is the effect on garbage collection. Since interned strings are strongly referenced by the pool, they are not eligible for collection. This can increase the live set and make GC pauses longer. The pool itself is a data structure that must be scanned during some GC phases, so a very large pool can slow down the JVM.

Common Pitfalls and Misconceptions

One frequent misconception is that intern() makes all strings comparable with ==. That is only true if both strings are interned. If you compare an interned string with a non-interned string using ==, you will get false even if the content is identical. Always use equals() for content comparison unless you are certain both operands are interned.

Another pitfall is interning strings that are not stable. For example, if you read a value from a database and intern it, you may inadvertently keep every distinct value alive forever. This is especially dangerous in long-running applications where the set of distinct values grows over time.

Also, note that intern() is a native method. In some JVM implementations, it may be slower than a simple equals() check for a one-time comparison. The cost is only justified when the same string will be reused many times.

Comparing intern() with a Custom String Cache

An alternative to intern() is to maintain your own cache, such as a HashMap<String, String> or a Set<String>. This gives you control over the lifecycle and eviction policy. For example, you could use a WeakHashMap to allow entries to be garbage collected when no other references exist.

private static final Map<String, String> cache = new WeakHashMap<>(); public static String canonicalize(String value) { return cache.computeIfAbsent(value, v -> v); }

This approach avoids the global string pool and allows you to limit the cache size or use weak references. However, it adds the overhead of a map lookup and the complexity of managing concurrency if used from multiple threads. The JVM's string pool is thread-safe and optimized for this exact purpose, but it is not configurable in terms of eviction.

Choosing between intern() and a custom cache depends on your requirements. Use intern() when you have a bounded set of well-known strings and you want the simplest implementation. Use a custom cache when you need to control memory usage or when the set of strings is unbounded.

Production Considerations and JVM Tuning

In production, the string pool size can be tuned with the -XX:StringTableSize flag. The default size is 60013 buckets, which is sufficient for many applications. If you know that your application will intern a large number of strings, you can increase this value to reduce hash collisions. Conversely, if you are not using interning heavily, the default is fine.

Monitoring the string pool is not straightforward. You can use JVM tools like jcmd or jstat to inspect the heap, but there is no direct metric for the number of interned strings. A sudden increase in heap usage that is not explained by application objects might indicate excessive interning.

A safer pattern is to avoid interning strings that come from external input unless you have a strict whitelist. For example, you might intern only strings that match a known set of enum-like values. This gives you the memory benefit without the risk of unbounded pool growth.

Ultimately, the java intern method is a tool that should be used deliberately. It is not a universal optimization. Understanding how the string pool works and what tradeoffs exist will help you decide when to call intern() and when to leave strings as ordinary heap objects.

java intern method: Practical Usage and Code Examples | RYUSLOG DEV