If you've ever analyzed a Java heap dump using Eclipse MAT or JProfiler, you've probably encountered a
shocking statistic: duplicate strings taking up 15% to 30% of your entire heap memory. Dozens
of identical strings containing values like "USER", "SUCCESS", or identical database
columns are scattered all over the heap.
This triggers a very logical question: "Wait, aren't Java Strings immutable? If they cannot be changed, why doesn't Java just reuse a single instance of them? How can duplicate, identical Strings even exist in the heap?"
In this post, we'll demystify this behavior in plain layman terms and show the code patterns that cause it, along with options to resolve it.
Imagine writing letters on paper and sealing them inside envelopes with hot wax:
- Immutability (The Wax Seal): Once a letter is sealed, its text cannot be changed. If you want to modify a detail in the letter, you must write a brand-new letter from scratch. The original remains unchanged.
- Uniqueness (The Master Archive): Just because a wax-sealed letter is immutable doesn't mean there is only one copy of it. If you write the exact sentence "PAY $50" on 10 separate pieces of paper, you have 10 identical, wax-sealed letters. They are all immutable, but they still take up 10 slots of physical drawer space on your desk!
Why Doesn't the String Constant Pool Save Us?
Java has a built-in mechanism called the String Constant Pool to optimize memory. However, it only works automatically on String Literals (hardcoded strings in your Java source files) that are known at compile time.
When you write:
String s1 = "hello";
String s2 = "hello";
The compiler knows both strings have the exact same value. It places one copy of "hello" in the
String Constant Pool, and makes both s1 and s2 point to the same memory address.
This is the **Master Archive** saving space.
But when strings are created **dynamically at runtime**, they bypass this compile-time library optimization completely.
How Duplicate Strings are Born (Code Patterns)
Pattern 1: The JDBC Driver & JSON Deserializer
This is the most common cause of duplicate strings in enterprise apps. When a database query returns 10,000 rows, or a JSON payload lists 500 orders, the parser extracts raw character data from the socket stream and calls a constructor to build string objects:
// Under the hood, parsers call the String constructor for every record:
String role1 = new String(charBuffer, 0, 4); // "USER"
String role2 = new String(charBuffer, 0, 4); // "USER"
Even though the strings represent the exact same characters, the new String() constructor
allocates a brand-new object on the heap, bypassing the String Pool entirely. Now you have two distinct
objects at different memory addresses, wasting twice the memory.
Pattern 2: Dynamic Runtime Modifications
When you modify, format, or concatenate strings at runtime, Java creates new String instances on the heap:
String path = "/api/v1/users/" + userId;
// Created on the heap dynamically. If called 1,000 times,
// 1,000 different heap objects are generated.
How to Fight Duplicate Strings
Solution 1: Explicitly Calling String.intern()
You can manually instruct Java to use the String Pool at runtime using the .intern() method.
This checks if an identical string already exists in the pool; if it does, it returns the pooled instance and
discards the new one:
// Manually pooling database values
String role = dbResult.getString("role").intern();
Warning: Use
intern()with caution. The String Pool is managed inside a lookup table. If you intern millions of unique strings (like user IDs), checking the pool introduces CPU overhead, and the table itself can run out of memory (Metaspace/PermGen).
Solution 2: G1 GC String Deduplication (Highly Recommended)
If you are using the G1 Garbage Collector (Java 8 Update 20 and higher), you can enable automatic deduplication at the JVM startup level using this parameter:
-XX:+UseG1GC -XX:+UseStringDeduplication
How it works: The Garbage Collector silently scans the heap during its idle phases. When it
finds two separate String objects containing identical character arrays (e.g. byte[] or
char[]), it modifies the second String object to point to the first String's character array, and
deletes the duplicate array. This removes the heaviest part of the duplicate strings from memory without any
application code changes!
Immutability vs Uniqueness Summary
| Concept | Analogy | Java Behavior |
|---|---|---|
| Immutability | Wax seal on an envelope | Once created, you cannot change the text inside the String object. |
| Uniqueness | Only one copy in the archives | Only guaranteed for String literals in the Constant Pool. |
| Heap Duplicates | Identical letters in multiple drawers | Occurs whenever strings are loaded at runtime from DBs, files, or API payloads. |