String immutability — why is String immutable in Java?
EasyA 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
- How it's enforced.
Stringis afinalclass. Its characters live in aprivate final byte[](since Java 9, Latin-1 or UTF-16 depending on content), and no method writes to that array. "Changing" methods liketoUpperCase,replace,concatreturn a newString. - 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.
- Building strings. For many appends, use
StringBuilder, which is mutable, and calltoString()once at the end.
Example
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 Stringvariable 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 callintern(). - Since Java 9,
a + bcompiles to aninvokedynamiccall toStringConcatFactory, 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()ors.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