Question 2 Β· Section 12

Difference Between Creating String via Literal and via new

There are two ways to create a string in Java, and they work differently:

Language versions: English Russian Ukrainian

🟒 Junior Level

There are two ways to create a string in Java, and they work differently:

Via literal (recommended):

String s = "Hello";

JVM checks the String Pool. If such a string already exists β€” returns a reference to it. If not β€” creates a new one in the pool.

Via constructor new (not recommended):

String s = new String("Hello");

Always creates a new object in regular heap, ignoring the String Pool for the object itself.

Example:

String s1 = "Hello";
String s2 = "Hello";
String s3 = new String("Hello");

System.out.println(s1 == s2); // true  (same object in pool)
System.out.println(s1 == s3); // false (s3 β€” separate object)
System.out.println(s1.equals(s3)); // true (content is the same)

When to use: In 99% of cases, use literals. The new String(...) constructor is mainly needed when converting from byte[]/char[], and also in rare cases for creating a guaranteed independent copy (defensive copying).


🟑 Middle Level

How it works internally

Literal:

String s = "Hello";
  • Bytecode: LDC instruction (Load Constant)
  • JVM checks String Pool by string hash
  • If found β€” returns reference, if not β€” creates object in pool
  • Optimization happens at class loading stage

Constructor:

String s = new String("Hello");
  • Bytecode: NEW β†’ DUP β†’ LDC "Hello" β†’ INVOKESPECIAL
  • The literal "Hello" is already in String Pool
  • new String(...) creates a second object in Regular Heap with the same content
  • Result: two objects in memory with identical text

Typical mistakes

  1. Mistake: new String("literal") for β€œguaranteed uniqueness” Solution: This is an antipattern. String object uniqueness is almost never needed

  2. Mistake: Thinking new String() is faster Solution: Literals are faster β€” they use the pool and don’t create extra objects

When the constructor is actually needed

| Case | Example | Explanation | | β€”β€”β€”β€”β€”β€”β€”- | ————————– | ————————– | | From byte[] | new String(bytes, UTF_8) | Main approach for I/O | | From char[] | new String(chars) | Character conversion | | From StringBuffer/Builder | builder.toString() | Implicit constructor call |


πŸ”΄ Senior Level

Internal Implementation

Literal bytecode:

0: ldc           #2  // String Hello
2: astore_1

One instruction β€” loading constant from class Constant Pool.

Constructor bytecode:

0: new           #3  // class java/lang/String    // NEW β€” allocates memory for new String object
3: dup                                        // DUP β€” duplicates reference for constructor
4: ldc           #2  // String Hello            // LDC β€” loads literal from Constant Pool
6: invokespecial #4  // Method ..."<init>":...  // INVOKESPECIAL β€” calls String constructor
9: astore_1

Four instructions: memory allocation, reference duplication, constant loading, constructor call.

Architectural Trade-offs

Why the constructor exists:

  • Historically: for creating strings from raw data (byte[], char[], int[] code points)
  • Practically: new String(String original) copies content into a new array (in Java 7+), guaranteeing independence from the original

Why an independent copy might be needed:

  • Before Java 7u6: substring() shared the parent’s char[]. new String(substring) copied the data, freeing the parent array for GC
  • In modern Java this is irrelevant β€” substring() always copies data

Edge Cases

  1. String with intern():
    String s = new String("Hello").intern();
    

    Creates an object in Heap, then intern() checks the pool. If "Hello" is already in pool β€” returns pool reference, and the Heap object becomes garbage.

  2. Constant Folding:
    String s = "Hel" + "lo"; // Compiler folds this into "Hello" at compile time
    

    Result will be in String Pool, as if you wrote "Hello".

  3. Runtime concatenation:
    String a = "Hel";
    String s = a + "lo"; // NOT in pool! Created via invokedynamic/StringBuilder
    

Performance

| Operation | Allocations | Time | GC pressure | | β€”β€”β€”β€”β€”β€”β€” | β€”β€”β€”β€”β€”β€”β€”β€”- | ——————– | β€”β€”β€”β€”- | | "Hello" | 0 (if in pool) | ~0 | None | | new String("Hello") | 1 object + literal in pool| ~10-20ns | Medium | | "Hello".intern() | 0 | Hash table lookup | Low |

Production Experience

In JSON/XML parsers, where the same field ("name", "id", "type") appears millions of times:

  • Without pool: millions of String objects β†’ significant GC overhead
  • With pool: dozens of unique strings β†’ 90%+ memory savings on strings

Monitoring

# See number of String objects
jmap -histo:live <pid> | grep java.lang.String

# Analysis via JOL
System.out.println(GraphLayout.parseInstance(s1).toFootprint());
System.out.println(GraphLayout.parseInstance(s2).toFootprint());

Best Practices

  • Never use new String("literal") without a justified reason
  • To move a string from Heap to Pool, use .intern()
  • If you need a guaranteed independent copy β€” new String(existingString) (though this is rarely required)

🎯 Interview Cheat Sheet

Must know:

  • Literal String s = "Hello" β€” JVM checks String Pool, returns reference from pool or creates in pool
  • new String("Hello") β€” always creates a NEW object in heap (even if literal is already in pool)
  • Literal uses LDC bytecode, constructor β€” NEW + DUP + LDC + INVOKESPECIAL
  • Constant folding: "Hel" + "lo" is folded by compiler into "Hello" at compile time
  • Runtime concatenation (a + "lo") β€” NOT in pool, created via invokedynamic/StringBuilder
  • new String("...") creates 2 objects: literal in pool + object in Heap

Frequent follow-up questions:

  • How many objects does new String("Hello") create? β€” One or two. The literal "Hello" is already in pool (on class load), new String() creates a second object in Heap.
  • Why does new String(String) constructor even exist? β€” For creating an independent copy (defensive copying). Before Java 7u6 this was needed to free memory from shared array.
  • Which is faster: literal or new String()? β€” Literal is faster β€” 0 allocations (if already in pool). new String() β€” ~10-20ns + GC pressure.
  • When is new String() from byte[]/char[] needed? β€” This is the main approach for I/O: new String(bytes, UTF_8), new String(chars).

Red flags (DON’T say):

  • ❌ β€œnew String("literal") creates a unique string” β€” creates a duplicate, not uniqueness
  • ❌ β€œLiteral and new String() β€” the same thing” β€” different memory placement
  • ❌ β€œnew String() is faster than literal” β€” opposite, literal is faster
  • ❌ β€œConstant folding works for variables” β€” only for static final constants and literals

Related topics:

  • [[1. How String Pool Works]]
  • [[3. When to Use intern()]]
  • [[9. Can You Use == to Compare Strings]]
  • [[7. What Happens When Concatenating Strings with + Operator]]