Java Immutable Strings
Table of Contents + −
In the last lesson you learned about Java StringBuffer. Along the way you kept hearing one word: a String is immutable. What does that actually mean, and why did Java’s makers choose it?
🤔 What does immutable even mean?
The pain: you call toUpperCase() on a lowercase name, print it, and nothing changed.
- Immutable means once a String is made, its contents can never change.
- A method like
toUpperCase()cannot edit the String, so it builds a brand new one and returns it. - Your original stays untouched.
- If you do not catch the new String, it is lost.
This is the whole idea in one short program.
public class Demo { public static void main(String[] args) { String name = "alex"; name.toUpperCase(); // makes "ALEX" then throws it away System.out.println(name); // still lowercase }}Walking through it:
toUpperCase()builds a new String “ALEX”.- We never store it, so it is created and immediately lost.
namestill points at the same old “alex”. That is immutability in action.
Output
alex🔁 Methods return a new String
If methods cannot change a String, what do they do? They return a new one.
- Every “modifying” method works this way:
concat,toUpperCase,replace,trim,substring. - Each builds a fresh String from the old one and gives it back.
- The original stays exactly as it was.
This proves it with three methods.
public class NewStrings { public static void main(String[] args) { String s = "Hello";
String upper = s.toUpperCase(); // new String "HELLO" String joined = s.concat(" World"); // new String "Hello World" String swapped = s.replace('l', 'L'); // new String "HeLLo"
System.out.println("upper: " + upper); System.out.println("joined: " + joined); System.out.println("swapped: " + swapped); System.out.println("original s: " + s); // unchanged }}Line by line:
toUpperCase()returns “HELLO” intoupper.concat(" World")returns “Hello World” intojoined.replace('l', 'L')returns “HeLLo” intoswapped.- Three new Strings were born, but
sis still “Hello”. It never changed, because it cannot.
Output
upper: HELLOjoined: Hello Worldswapped: HeLLooriginal s: HelloThe takeaway: a String method gives you a result, it does not update what you called it on. Always look at the return value.
💾 You must reassign to keep the result
You do not “change” a String. You point your variable at the new String the method returns. That is called reassigning.
Here is the fix for that first program.
public class Reassign { public static void main(String[] args) { String name = "alex"; name = name.toUpperCase(); // ✅ catch the new String, point name at it System.out.println(name); // now uppercase }}The line name = name.toUpperCase():
- First the method builds the new String “ALEX”.
- Then the
=pointsnameat it. The old “alex” is forgotten. - The variable moved. The String itself still never changed.
Output
ALEXYou can chain methods too. This trims, lowercases, then replaces, all stored back into one variable.
String input = " Hello World ";input = input.trim().toLowerCase().replace(' ', '-');System.out.println(input); // hello-worldHow the chain works:
- Each method returns a new String, and the next method runs on that.
- At the end, the
=pointsinputat the final result. - Without that
=, every result would be thrown away.
Output
hello-world🔐 Why is String immutable?
It feels like extra work, but it was a deliberate choice that buys a lot of safety.
- Safe sharing in the string pool. Java stores text literals in the string pool and hands the same String object to parts that use the same text. This is safe only because no one can change it.
- Thread safety. A String can never change, so two threads can never corrupt it. Many threads read it freely with no locks.
- Safe as HashMap keys. A HashMap finds a key by its hashcode. A changeable key could change its hashcode and get lost. A String key stays findable forever.
- Cached hashcode. Since a String never changes, Java computes its hashcode once and reuses it. That makes String keys fast.
- Security. Paths, addresses, and usernames travel as Strings. A changeable one could pass a security check, then be swapped for a bad value. Immutability closes that door.
One choice, many wins
Immutability is not one feature. It is a single design decision that quietly gives you safe sharing, thread safety, reliable map keys, a cached hashcode, and security all at once. That is why String works this way.
🧠 Reference vs contents
Separate two ideas: the variable and the String it points at.
- A String variable does not hold the text. It holds a reference, a pointer to a String object in memory.
- The text lives in that object.
- When you reassign, you move the pointer to a different object. The old object’s contents never changed; you just stopped looking at it.
This program makes the difference clear.
public class RefVsContents { public static void main(String[] args) { String a = "Hi"; String b = a; // b points at the same String as a
a = a.concat("!"); // a now points at a NEW String "Hi!"
System.out.println("a = " + a); // Hi! System.out.println("b = " + b); // Hi }}The story:
apoints at “Hi”. Thenb = amakesbpoint at that same “Hi” object.a = a.concat("!")builds a new “Hi!” and pointsaat it.bwas never touched, so it still points at the original “Hi”. The contents survived.
Output
a = Hi!b = HiSo “you can’t change a String” means the contents are frozen. You can always point a variable somewhere new. Two different things.
🐢 The cost: building strings in a loop
Immutability has one real cost.
- Every
+on a String creates a new String, it does not edit one. - Once is fine. Thousands of times in a loop and the work piles up.
This loop joins numbers with +. Each pass quietly makes a whole new String.
String result = "";for (int i = 1; i <= 1000; i++) { result = result + i + " "; // ❌ a brand new String every single pass}What each pass does:
result + icreates a new String, copies all the old characters in, adds the new piece, and discards the old one.- By pass 500 the text is long, so Java copies all of it again just to add a little.
- The work grows as the text grows. Correct, but slow for large counts.
The fix is the StringBuilder, a changeable text buffer made for this. It keeps one buffer and extends it, with no repeated copying.
StringBuilder sb = new StringBuilder();for (int i = 1; i <= 1000; i++) { sb.append(i).append(" "); // ✅ same buffer, no throwaway Strings}String result = sb.toString();How this fixes it:
- Each
appendadds to the same buffer. No new String per pass, no repeated copying. - One
toString()at the end gives you the finished String. - For heavy building, this is the right tool. For a couple of joins, plain
+is fine.
The simple rule
A few joins with + are fine and clear. Building text across many passes, especially in a loop, calls for a StringBuilder. Immutability is the reason the loop with + gets slow.
⚠️ Common Mistakes
The two classic immutability traps.
Calling a method and ignoring the result. The method ran, but you threw its return value away.
String s = "hello world";
// ❌ does nothing useful: the new String is thrown aways.toUpperCase();s.replace(' ', '_');System.out.println(s); // still "hello world"
// ✅ catch the result by reassignings = s.toUpperCase();s = s.replace(' ', '_');System.out.println(s); // HELLO_WORLDThe first block returns new Strings that are immediately lost, so s cannot change. The fixed block stores each result back into s, so the changes stick.
Building strings with + in a big loop. Correct, but slow, because immutability forces a new String every pass.
// ❌ slow for large counts: a new String each passString out = "";for (int i = 0; i < 100000; i++) out = out + i;
// ✅ fast: one buffer, then convert onceStringBuilder sb = new StringBuilder();for (int i = 0; i < 100000; i++) sb.append(i);String out = sb.toString();The + version copies the whole growing text every pass. The StringBuilder version edits one buffer and makes a String only at the end.
The silent bug
A String method that returns a value but goes unused is silent. The code compiles and runs, it just does not do what you meant. Whenever you call a String method to transform text, assign the result somewhere.
✅ Best Practices
Habits for working with immutable Strings.
- Always use the return value. A String method gives you a new String. Store it, usually with
s = s.method(). - Reassign to “change” text. You move the variable to the new String; the old contents never change.
- Use StringBuilder for heavy building. Many passes in a loop call for a buffer, not
+. - Trust String as a map key. Because it cannot change, it is a safe and fast HashMap key.
- Lean on thread safety. Many threads can read the same String with no locks and no risk.
- Keep
+for simple joins. A couple of joins are clear and fast enough; do not over-engineer.
🧩 What You’ve Learned
Great work. Let’s recap immutable Strings.
- ✅ Immutable means a String’s contents can never change once it is made.
- ✅ Methods like
concat,toUpperCase, andreplacereturn a new String and leave the original untouched. - ✅ To keep a result you must reassign, like
s = s.toUpperCase(). - ✅ A variable holds a reference, not the text; reassigning moves the pointer, it does not edit contents.
- ✅ String is immutable for safe sharing, thread safety, reliable map keys, a cached hashcode, and security.
- ✅ The cost is that
+in a loop makes a new String each pass; use a StringBuilder there.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does it mean that a String is immutable?
Why: Immutable means the contents of a String object are fixed once it is created.
- 2
What does name.toUpperCase() do to the variable name on its own?
Why: It returns a new uppercase String. Unless you reassign it, name is unchanged.
- 3
Which is a reason String is immutable?
Why: Immutability gives safe sharing in the pool, thread safety, reliable map keys, a cached hashcode, and security.
- 4
Why can building text with + in a big loop be slow?
Why: Because String cannot change, each + creates a new String and copies the old text; StringBuilder avoids this.
🚀 What’s Next?
You now know why a String never changes and how to work with that. Next up: regular expressions, a tool for matching patterns inside text. They let you search, validate, and pull pieces out of Strings with short, precise patterns.