Constant Pools in Java

Overview of Java Constant Pools

The term "constant pool" in Java most commonly refers to the runtime constant pool, which is part of the method area. There is exactly one runtime constant pool per JVM instance, shared across all running threads.

The constant pool stores data that is resolved during the compile phase. This includes values of final constants (member constants, local constants, and reference constants) as well as object literals.

During compilation, when a new constant is assigned a value, the JVM first checks if an identical value already exists in the constant pool. If it exists, the reference (memory address) of the existing entry is returned directly. If not, a new entry for the value is created in the pool before the reference is returned. This design guarantees that no duplicate values exist in the constant pool.

Final Constants

Any variable declared with the final keyword is treated as a constant. All final constents must be initialized at the time of declaration, otherwise the code will fail to compile.

Object Literals

An object literal is an object created by assigning a constant value directly, rather than instantiating a new object with the new keyword in the heap space.

The two most common types of object literals are primitive wrapper class literals and String object literals.

Primitive Wrapper Class Constant Pooling

Most of Java's primitive wrapper classes implement constant pool caching: Byte, Short, Integer, Long, Character, and Boolean all pre-cache values in the range [-128, 127] by default. Any value outside of this range will still create a new separate object. The two floating-point wrapper classes Float and Double do not implement constant pooling at all.

The example below demonstrates constant pool behavior with Integer:

Integer val1 = 40;
Integer val2 = 40;
Integer val3 = 0;
Integer val4 = new Integer(40);
Integer val5 = new Integer(40);
Integer val6 = new Integer(0);

System.out.println("val1 == val2: " + (val1 == val2));
System.out.println("val1 == val2 + val3: " + (val1 == val2 + val3));
System.out.println("val1 == val4: " + (val1 == val4));
System.out.println("val4 == val5: " + (val4 == val5));
System.out.println("val4 == val5 + val6: " + (val4 == val5 + val6));  
System.out.println("40 == val5 + val6: " + (40 == val5 + val6));

Output:

val1 == val2: true
val1 == val2 + val3: true
val1 == val4: false
val4 == val5: false
val4 == val5 + val6: true
40 == val5 + val6: true

Explanation:

  • When creating Integer val1 = 40 with a literal, the JVM first checks the constant pool for the value 40, and reuses the existing reference if it exists.
  • Declaring new Integer(40) always allocates a brand new object in heap memory, regardless of existing constant pool entries.
  • For the expression val4 == val5 + val6: the + operator cannot operate directly on Integer objects, so val5 and val6 are automatically unboxed to primitive int values, which are then summed. When comparing an Integer object to a primitive int, the Integer is also unboxed, so the final comparison becomes 40 == 40, which evaluates to true.

String Object Literals

String s1 = "abcd";
String s2 = new String("abcd");
System.out.println(s1 == s2); // false

String part1 = "str";
String part2 = "ing";
String full1 = "str" + "ing";
String full2 = part1 + part2;
System.out.println("string" == "str" + "ing"); // true
System.out.println(full1 == full2); // false

String full3 = "string";
System.out.println(full1 == full3); // true

Explanation:

  • The reference s1 points directly to the entry for "abcd" stored in the constant pool, while new String("abcd") creates a new String object in heap memory. Any object created with new will always have a unique memory address.
  • For + concatenation: only concatenation of multiple string literals (static text wrapped in quotes) is resolved at compile time, and the resulting string is added to the constant pool.
  • Concatenation involving string variables is resolved at runtime, the resulting new string is not added to the constant pool, so it has an independent memory address that does not match the precomputed constant pool entry.

The String.intern() Method

The intern() method can be used to force add a string to the constant pool:

public static void main(String[] args) {
    String heapStr = new String("cloud computing");
    String pooledStr = heapStr.intern();
    String literalStr = "cloud computing";
    System.out.println("heapStr == pooledStr? " + (heapStr == pooledStr));
    System.out.println("literalStr == pooledStr? " + (literalStr == pooledStr));
}

Output:

heapStr == pooledStr? false
literalStr == pooledStr? true

Explanation:

intern() searches the constant pool for an existing string equal to the current string (checked via equals()). If a matching entry exists, it returns the reference of the existing entry. If no match exists, it adds the current string to the constant pool before returning its reference.

Note: final constants require initialization at declaration, but object literals can be declared first and assigned later.

Benefits of Constant Pools

Constant pooling is designed to avoid performance degradation caused by frequent creation and destruction of identical objects, by enabling shared access to immutable constant values.

  • Reduced memory usage: All identical constant values are merged into a single entry, so only one memory allocation is needed for repeated use of the same value.
  • **Faster comparisons: Comparing references with == is significantly faster than comparing values with equals(). If two constant references point to the same pool entry, their values are guaranteed to be equal.

Note: When used with primitive types, == compares the actual value. When used with reference types, == compares the memory address of the object that the reference points to.

Tags: java JVM Constant Pool

Posted on Wed, 13 May 2026 01:35:24 +0000 by Duey