ByteScrollGet the app
☰ Topics
string-immutability14 / 200‹›
JAVA / STRINGS2 minute read

String immutability — why is String immutable in Java?

Easy

A String can't change after it's built, so the JVM can share one copy of a literal through the string pool, cache its hash code, and pass it between threads with no locks. A checked file name or class name also can't be swapped out after the check.

How it works

  1. How it's enforced. String is a final class. Its characters live in a private final byte[] (since Java 9, Latin-1 or UTF-16 depending on content), and no method writes to that array. "Changing" methods like toUpperCase, replace, concat return a new String.
  2. Why it's designed that way:
    • String pool. Identical literals in a program point to one shared object. That's only safe if no one can modify it.
    • Hash caching. hashCode() is computed once and stored in a field. Strings are the most common map key, so this saves a lot of work.
    • Thread safety. Immutable objects can be shared across threads without synchronization.
    • Security. File paths, URLs, hostnames and class names are passed around as strings. If a caller could mutate one after a permission check, the check would be meaningless.
  3. Building strings. For many appends, use StringBuilder, which is mutable, and call toString() once at the end.
unchanged, b still sees'shop'String a ="shop"pool: 'shop'String b ="shop"a =a.toUpperCase()new object'SHOP'
unchanged, b still sees'shop'String a ="shop"pool: 'shop'String b ="shop"a =a.toUpperCase()new object'SHOP'

Example

Example.javaJava
String city = "lisbon";
city.toUpperCase();                 // result thrown away; city is still "lisbon"
String shout = city.toUpperCase();  // "LISBON": a new object

String x = "lisbon";
boolean shared = (city == x);       // true: both point to the pooled literal

// Many appends: one mutable buffer, one final String
StringBuilder report = new StringBuilder();
for (int day = 1; day <= 7; day++) {
    report.append("day ").append(day).append(": ok\n");
}
String text = report.toString();

Edge cases

  • A final String variable can't be reassigned, but that's about the reference. Immutability is about the object, and the two are separate ideas.
  • Strings built at runtime (new String(...), concatenation of non-constants) aren't pooled unless you call intern().
  • Since Java 9, a + b compiles to an invokedynamic call to StringConcatFactory, so a single concatenation expression is already efficient. Loops are still the problem.
  • Reflection could once overwrite the internal array. Strong encapsulation of JDK internals (Java 16/17) blocks that without --add-opens.

Common mistakes

  • Calling s.trim() or s.replace(...) and not using the return value.
  • Storing passwords in String. They can't be wiped and may linger in the heap; char[] can be zeroed after use.
  • Concatenating with += in a large loop, creating a new string every iteration.

Likely follow-up

"How would you write your own immutable class?" Make the class final, all fields private final, set them in the constructor, provide no setters, and defensively copy any mutable input or output (arrays, lists, dates). Or use a record with immutable component types.

Get every deep dive in the app

Coming soon to the App StoreComing soon to Google Play