StackWise
Modern JavaFundamentals

Strings, StringBuilder & the String Pool

Why String is immutable, how the string pool and interning work, == vs equals, and efficient string building.

beginner 3 min readLesson 6 of 26

String is immutable

Every “modification” of a String — concat, replace, toUpperCase — returns a new object. The original never changes.

String s = "hello";
s.toUpperCase();          // result discarded
System.out.println(s);    // "hello"
s = s.toUpperCase();      // reassign the reference to the new object

Why immutability?

  • String pool — identical literals can be safely shared
  • Security — file paths, URLs, class names and DB credentials can’t be changed after validation
  • Thread safety — immutable objects need no synchronisation
  • Hash caching — hashCode() is computed once, making strings excellent HashMap keys

The string pool

String literals are stored in a pool (in the heap since Java 7). The JVM reuses the same object for identical literals; new String(...) always creates a new object.

StringPool.javaJava 21
class Main {
    public static void main(String[] args) {
        String a = "java";
        String b = "java";
        String c = new String("java");
        String d = c.intern();
        String e = "ja" + "va";              // compile-time constant -> folded into "java"
        String part = "ja";
        String f = part + "va";              // computed at runtime -> new object

        System.out.println("a == b        " + (a == b));        // same pooled literal
        System.out.println("a == c        " + (a == c));        // c is a new heap object
        System.out.println("a.equals(c)   " + a.equals(c));     // same characters
        System.out.println("a == d        " + (a == d));        // intern() returns the pooled one
        System.out.println("a == e        " + (a == e));
        System.out.println("a == f        " + (a == f));
        System.out.println("a == f.intern " + (a == f.intern()));
    }
}

Concatenation performance

In a loop, += creates a new string every iteration — O(n²) copying:

String csv = "";
for (String s : items) csv += s + ",";        // slow for large lists

Use StringBuilder (not thread-safe, fast) — or better, a higher-level API:

var sb = new StringBuilder();
for (String s : items) sb.append(s).append(',');

String joined = String.join(",", items);
String joined2 = items.stream().collect(Collectors.joining(", ", "[", "]"));
String StringBuilder StringBuffer
Mutable No Yes Yes
Thread-safe Yes (immutable) No Yes (synchronised)
Use when Values, keys, constants Building strings in one thread Legacy code — rarely needed

Useful modern String APIs

"  hi  ".strip();                // "hi" — Unicode-aware (Java 11), prefer over trim()
"".isBlank();                    // true (Java 11)
"ab".repeat(3);                  // "ababab" (Java 11)
"a\nb\nc".lines().count();       // 3 (Java 11)
"Hello %s".formatted("Ada");     // "Hello Ada" (Java 15)
"ABC".chars().sum();             // IntStream of code units
"hello".indent(4);               // adds 4 spaces to each line (Java 12)

Text blocks (Java 15) make multi-line strings readable:

String json = """
    {
      "name": "Ada",
      "role": "admin"
    }
    """;

Compact strings

Since Java 9, a String stores its characters in a byte[] with a coder flag: Latin-1 strings use one byte per character instead of two, roughly halving memory for typical English text. This is invisible to your code.

Finished this lesson?

Mark it complete to track your progress through Modern Java.