The name that wouldn't change
Riya is making name badges for the college fest. Names must be printed in capitals. She writes name.toUpperCase(); and then System.out.println(name);. The badge still says riya.
Sir, toUpperCase() doesn't work! My name is still small letters.
It worked perfectly. It made a new String, “RIYA”, and handed it back to you. You just didn't catch it. Write name = name.toUpperCase();.
So it can't change the original “riya”?
No method can. Java Strings are immutable: once created, their characters never change. Every “change” gives you a new String. Today we'll see why Java chose that, and why it matters in real code.
What is a String?
A String is an object of the final class java.lang.String that represents an immutable sequence of characters. Once a String object is created, its contents cannot be changed; methods such as toUpperCase(), concat() and replace() return a new String. Characters are indexed from 0 to length() - 1.
A String is text written in permanent ink. You can read it, copy it, or make a new card with changes, but you can never edit the card you already have.
Words you'll meet in this lesson
"riya".+ or concat().'R'."": a real String with length 0.NullPointerException.String facts at a glance
| Class | java.lang.String (imported automatically), declared final: no subclass can change its behaviour |
|---|---|
| Length | s.length() (a method, with brackets) |
| One character | s.charAt(i) returns a char; valid i is 0 to length() - 1 |
| Join | "Hi " + name; any value joined to a String becomes text |
| “Changing” methods | toUpperCase, trim, replace, concat, substring … all return a new String |
| Inside (Java 9+) | A private final byte[] holding the characters, with no method that writes to it |
Exam tip: String s = "Hi"; s.concat("!"); System.out.println(s); prints Hi. The result of concat was thrown away. This exact question appears in many MCQ tests.
Ways to create a String
Literal
String city = "Pune";
The usual way. Literals are shared from the String pool (next lesson).
new String(…)
new String("Pune") or new String(charArray).
Always creates a fresh object.
From other values
String.valueOf(42), "" + 3.5, Integer.toString(7).
Numbers and booleans to text.
Concatenation goes left to right
Once a String appears, every following + joins text. Before that, numbers are added normally.
public class Main {
public static void main(String[] args) {
System.out.println(1 + 2 + "3" + 4 + 5);
System.out.println("Total: " + 10 + 20);
System.out.println("Total: " + (10 + 20));
System.out.println('A' + 'B' + "C");
}
}Last line: 'A' + 'B' are chars, so they're added as numbers first (65 + 66 = 131) before the String appears.
Why did Java make Strings immutable?
| Reason | What it gives you |
|---|---|
| Sharing (String pool) | Thousands of variables can safely share one "Pune" object, saving memory. |
| Security | A file path, URL or username checked for safety can't be changed afterwards by other code. |
| Thread safety | Many threads can read the same String with no locks. |
| Fast HashMap keys | The hash code never changes, so String caches it and makes a reliable map key. |
The cost: every “change” creates a new object. Building a long text with += inside a big loop creates thousands of throwaway Strings. For that, use StringBuilder (Lesson 4.6).
What really happens when you “change” a String
Create
A String object holding r-i-y-a exists on the heap; the variableString name = "riya";nameholds its address.Call a “changing” method
The method reads the old characters, creates a new String “RIYA”, and returns its address. Nobody stores it, so it becomes garbage.name.toUpperCase();Catch the result
Now the variablename = name.toUpperCase();nameis updated to point to the new object. The variable changed; the old String did not.Read characters safely
for (int i = 0; i < name.length(); i++) { System.out.println(name.charAt(i)); }charAtis read-only. There is nosetCharAton String;name.charAt(0) = 'r'doesn't even compile.Watch out for null
String s = null; s.length();compiles but throwsNullPointerException.""is safe: its length is 0.nullmeans the variable refers to no object, so there is nothing to calllength()on.
Variables move, Strings stay: both views
Left: the code and what prints. Right: the variables on the stack and the String objects on the heap.
- Create:
namerefers to the String “riya”. - Call:
toUpperCase()returns “RIYA”, but we didn't store it, sonameis still “riya”. - Reassign: storing the result makes
namerefer to “RIYA”. - Share:
other = namecopies the reference. Both print “RIYA”. - Join:
name + " S"builds a new String.otherstill sees “RIYA”, because that object never changed. - Try to edit: there is no way to assign into a String's characters. javac rejects it.
- The variable stores only a reference; the characters live in a String object on the heap.
toUpperCase()read “riya” and built a separate “RIYA” object. With no reference to it, the garbage collector can remove it.- Assignment changes which object
namepoints to. The old object is exactly as it was. - Copying a String variable copies the reference. Since Strings never change, sharing is completely safe.
- Concatenation always creates a new String. That's why
otheris unaffected. - The characters are stored in a
private final byte[]that String never writes to after construction, and the class isfinalso no subclass can add a setter.
Strings in real systems
Almost all input and output is text, and immutability makes Strings safe to pass around everywhere.
Watch it with an example: is this password strong?
A sign-up form needs at least 8 characters, one digit and one capital letter. Watch the loop read the String one character at a time with charAt(i).
- A String is a sequence of chars with indexes 0 to
length() − 1. Length 9 passes the first rule. - i = 0:
pw.charAt(0)is 'R'.Character.isUpperCase('R')is true → has a capital. - i = 1 … 4: 'i', 'y', 'a', '@': not digits. The loop keeps going.
- i = 5: '2'.
Character.isDigit('2')is true → has a digit. The loop could stop now. - Verdict: all three rules pass. Real sign-up forms run checks like this (plus more) before saving a password, and store only a hash, never the text.
- Read-only:
charAtonly reads. A String object can never be changed after it is created; that's immutability.
1. Form input
Names, emails, phone numbers and addresses arrive as Strings. Code trims, validates and normalises them, each step returning a new, cleaner String.
2. Messages and notifications
SMS, emails and push notifications are built by joining fixed text with values: names, amounts, masked numbers, links.
3. Keys in maps and caches
User IDs, product codes and session IDs are used as HashMap keys. Because a String can't change, its hash code stays the same and the entry is always found.
4. Security checks
A file path or URL is checked (“is this allowed?”) and then used. If Strings could change, other code could swap the value between the check and the use. Immutability closes that gap.
Examples are simplified to show where String behaviour matters.
Name badges, done right
public class Main {
public static void main(String[] args) {
String name = "riya sharma";
name.toUpperCase(); // result ignored
System.out.println("Oops: " + name);
name = name.toUpperCase(); // result stored
System.out.println("Badge: " + name);
System.out.println("Letters on badge: " + name.length());
System.out.println("Initial: " + name.charAt(0));
}
}name.toUpperCase();Creates “RIYA SHARMA” and throws it away.nameis untouched.name = name.toUpperCase();Stores the new String in the variable. The usual pattern.name.length()11: the space counts as a character.name.charAt(0)Returns achar, here'R'.
Reversing a word creates a new String every turn
Watch the heap: each rev + … leaves the old String behind and makes a new one.
Worked examples: real problems, solved and traced in memory
Three real situations where String immutability decides whether the code works. Run each one, then step through it and watch new String objects appear on the heap while the originals never change.
Clean up a name for the badge
Problem statement
Names typed at the registration desk arrive messy, like " riya ". A method badgeName must return the name trimmed and in capitals. Print the badge and show that the original input is unchanged.
raw = " riya "
[RIYA] [ riya ]
Approach
- Call
trim()andtoUpperCase(): each returns a new String. - Return the final String from the method; the caller stores it in a new variable.
- Print both, to prove the original was never modified.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Joining SKU codes with +=
Problem statement
A warehouse prints a pick list by joining SKU numbers 1 to 3 into one line like SKU-1|SKU-2|SKU-3. Build it with += and see what that does in memory.
n = 3
SKU-1|SKU-2|SKU-3
Approach
- Start with an empty String.
- In each loop pass, add a separator (except the first time) and the next code.
- Watch the heap: every
+=creates a brand-new String. That's why Lesson 4.6 uses StringBuilder for long loops.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Why the method couldn't change the name
Problem statement
A developer wrote addTitle(name) to put “Mr. ” in front of a customer's name before printing a letter, but the letter still shows the plain name. Trace why, then compare with the fixed version that returns the new String.
name = "Arjun"
Broken: Arjun Fixed: Mr. Arjun
Approach
- The method receives a copy of the reference. Reassigning that copy inside the method doesn't touch the caller's variable.
- Strings can't be changed in place, so the only way to give back a new name is to return it.
- The caller must store the returned value.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Check yourself
Quick revision
Lesson 4.3 in one screen
- A String is an object of the final class
java.lang.String: an immutable sequence of characters. - “Changing” methods return a new String: always write
s = s.method();. length()with brackets;charAt(i)for i from 0 tolength() - 1.+works left to right; once a String appears, the rest is joined as text.- Reassigning a variable moves the reference; the old String is untouched.
- Why immutable: pool sharing, security, thread safety, cached hash codes.
- Cost: heavy concatenation in loops creates many objects → use StringBuilder.
Interview and viva questions
Why are Strings immutable in Java?
So they can be safely shared from the String pool, used in security checks without being changed afterwards, read by many threads without locks, and used as HashMap keys with a hash code that can be cached because it never changes.
How is String made immutable internally?
The class is final (no subclass can add mutators), the character data is a private final array, no method writes to it after construction, and methods that “modify” return new String objects.
If Strings are immutable, how does s = s + "x" work?
It creates a new String containing the old characters plus "x" and stores its reference in s. The variable changes; the original object doesn't.
What is the difference between "" and null?
"" is a real String object with length 0; you can call methods on it. null means no object; calling any method throws NullPointerException.
Why are passwords often kept in char[] instead of String?
A String can't be erased: it stays in memory until garbage-collected and may show up in heap dumps or logs. A char[] can be overwritten with zeros as soon as it has been used. Swing's JPasswordField.getPassword() returns char[] for this reason.
Practice problems
These problems use only length(), charAt(), loops and +, so you really understand what the built-in methods (Lesson 4.5) do for you. The last three are real business tasks.
Count the vowels
Count how many vowels (a, e, i, o, u, either case) a sentence has.
text = "Java Is Fun To Learn"
Vowels: 7
Show solution and explanation
public class Main {
public static void main(String[] args) {
String text = "Java Is Fun To Learn";
int count = 0;
for (int i = 0; i < text.length(); i++) {
char ch = Character.toLowerCase(text.charAt(i));
if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') {
count++;
}
}
System.out.println("Vowels: " + count);
}
}How it works: charAt(i) gives one char at a time, from index 0 to length() - 1. Lower-casing each character first means we only need to compare with five letters.
Reverse a string
Print a word backwards.
word = "Phatshala"
alahstahP
Show solution and explanation
public class Main {
public static void main(String[] args) {
String word = "Phatshala";
String reversed = "";
for (int i = word.length() - 1; i >= 0; i--) {
reversed = reversed + word.charAt(i);
}
System.out.println(reversed);
}
}How it works: walk from the last index down to 0, appending each character. Because Strings are immutable, every + creates a new String; fine for short words, but for long text use StringBuilder.reverse() (Lesson 4.6).
Palindrome check
Check whether a word reads the same forwards and backwards, ignoring case.
word = "Malayalam"
Malayalam is a palindrome
Show solution and explanation
public class Main {
public static void main(String[] args) {
String word = "Malayalam";
String w = word.toLowerCase();
boolean palindrome = true;
for (int i = 0, j = w.length() - 1; i < j; i++, j--) {
if (w.charAt(i) != w.charAt(j)) {
palindrome = false;
break;
}
}
System.out.println(word + (palindrome ? " is" : " is not") + " a palindrome");
}
}How it works: two indexes move towards each other, comparing pairs. We stop at the first mismatch, and we only check half the word. toLowerCase() returns a new String, so we store it in w.
Letters, digits and spaces
Count letters, digits, spaces and other characters in an address line.
addr = "Flat 12B, MG Road 560001"
Letters 11, digits 8, spaces 4, others 1
Show solution and explanation
public class Main {
public static void main(String[] args) {
String addr = "Flat 12B, MG Road 560001";
int letters = 0, digits = 0, spaces = 0, others = 0;
for (int i = 0; i < addr.length(); i++) {
char ch = addr.charAt(i);
if (Character.isLetter(ch)) letters++;
else if (Character.isDigit(ch)) digits++;
else if (ch == ' ') spaces++;
else others++;
}
System.out.println("Letters " + letters + ", digits " + digits + ", spaces " + spaces + ", others " + others);
}
}How it works: the Character class has helpers like isLetter and isDigit. An if-else-if ladder puts each character in exactly one bucket.
Toggle case
Change upper case letters to lower case and lower to upper. Leave other characters alone.
s = "HeLLo World 2026"
hEllO wORLD 2026
Show solution and explanation
public class Main {
public static void main(String[] args) {
String s = "HeLLo World 2026";
String result = "";
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (Character.isUpperCase(ch)) ch = Character.toLowerCase(ch);
else if (Character.isLowerCase(ch)) ch = Character.toUpperCase(ch);
result += ch;
}
System.out.println(result);
}
}How it works: we can't change a character inside s (Strings are immutable), so we build a new String. Digits and spaces are neither upper nor lower case, so they pass through unchanged.
How many times does a character appear?
Count how many times a given character appears in a string, and list the positions.
s = "banana", target = 'a'
'a' appears 3 times at index 1 3 5
Show solution and explanation
public class Main {
public static void main(String[] args) {
String s = "banana";
char target = 'a';
int count = 0;
String positions = "";
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == target) {
count++;
positions += i + " ";
}
}
System.out.println("'" + target + "' appears " + count + " times at index " + positions.trim());
}
}How it works: chars are compared with == because char is a primitive. (Strings are objects and must be compared with equals, next lesson.) trim() removes the last extra space.
Anagram check
Two words are anagrams if they use exactly the same letters the same number of times. Check “listen” and “silent”.
a = "listen", b = "silent"
listen and silent are anagrams
Show solution and explanation
public class Main {
public static void main(String[] args) {
String a = "listen", b = "silent";
boolean anagram = a.length() == b.length();
int[] count = new int[26];
for (int i = 0; anagram && i < a.length(); i++) {
count[a.charAt(i) - 'a']++;
count[b.charAt(i) - 'a']--;
}
for (int c : count) {
if (c != 0) anagram = false;
}
System.out.println(a + " and " + b + (anagram ? " are" : " are not") + " anagrams");
}
}How it works: ch - 'a' turns a lower-case letter into 0–25, an index into a counting array (Lesson 4.1). Letters of a add 1, letters of b subtract 1. If every count ends at 0, the letters match. One pass, no sorting.
Capitalise each word
Make the first letter of every word upper case and the rest lower case.
name = "rIYA sHARMA kumar"
Riya Sharma Kumar
Show solution and explanation
public class Main {
public static void main(String[] args) {
String name = "rIYA sHARMA kumar";
String result = "";
boolean startOfWord = true;
for (int i = 0; i < name.length(); i++) {
char ch = name.charAt(i);
if (ch == ' ') {
startOfWord = true;
result += ch;
} else if (startOfWord) {
result += Character.toUpperCase(ch);
startOfWord = false;
} else {
result += Character.toLowerCase(ch);
}
}
System.out.println(result);
}
}How it works: a boolean flag remembers whether the next letter starts a word. A space sets it back to true. This is how forms tidy up names typed in random case.
Mask a mobile number
For an SMS or receipt, show only the last 4 digits of a 10-digit mobile number; replace the rest with X.
mobile = "9876543210"
OTP sent to XXXXXX3210
Show solution and explanation
public class Main {
public static void main(String[] args) {
String mobile = "9876543210";
String masked = "";
for (int i = 0; i < mobile.length(); i++) {
masked += (i < mobile.length() - 4) ? 'X' : mobile.charAt(i);
}
System.out.println("OTP sent to " + masked);
}
}How it works: positions before the last four become 'X'. Banks and delivery apps mask phone numbers, card numbers and account numbers this way so a screenshot or printed receipt doesn't reveal them.
Validate a username
A sign-up form allows usernames that are 5 to 15 characters long, start with a letter, and contain only letters, digits or underscore. Check several usernames.
"riya_99", "9lives", "ab", "hello world", "Kabir_Dev"
riya_99 -> OK 9lives -> must start with a letter ab -> must be 5-15 characters hello world -> invalid character ' ' Kabir_Dev -> OK
Show solution and explanation
public class Main {
public static void main(String[] args) {
String[] names = {"riya_99", "9lives", "ab", "hello world", "Kabir_Dev"};
for (String u : names) {
String problem = "";
if (u.length() < 5 || u.length() > 15) {
problem = "must be 5-15 characters";
} else if (!Character.isLetter(u.charAt(0))) {
problem = "must start with a letter";
} else {
for (int i = 0; i < u.length(); i++) {
char ch = u.charAt(i);
if (!(Character.isLetterOrDigit(ch) || ch == '_')) {
problem = "invalid character '" + ch + "'";
break;
}
}
}
System.out.println(u + " -> " + (problem.isEmpty() ? "OK" : problem));
}
}
}How it works: check the cheapest rule (length) first, then the first character, then every character. Returning a reason, not just true/false, lets the form tell the user what to fix. Real systems repeat this check on the server; never trust only the browser.
Generate invoice numbers
Invoice numbers look like INV-2026-00042: a prefix, the year, and the serial number padded with zeros to 5 digits. Generate them for a few serials.
year = 2026, serials = 7, 42, 1350, 98765
INV-2026-00007 INV-2026-00042 INV-2026-01350 INV-2026-98765
Show solution and explanation
public class Main {
public static void main(String[] args) {
int year = 2026;
int[] serials = {7, 42, 1350, 98765};
for (int n : serials) {
String num = String.valueOf(n);
while (num.length() < 5) {
num = "0" + num;
}
System.out.println("INV-" + year + "-" + num);
}
}
}How it works: String.valueOf turns the number into text so we can check its length and add leading zeros. Fixed-width IDs sort correctly as text (00042 before 01350). In real code, String.format("%05d", n) does the padding in one call (Lesson 4.5).
Real-world scenario questions (asked in company interviews)
String bugs in production often come from forgetting immutability. Decide your answer first, then open the model answer.
“Email not registered” for a registered user
Users who type " Riya@Mail.com" (with a space and capitals) can't log in, though they registered as riya@mail.com. The code has email.trim(); email.toLowerCase(); before the lookup. Why doesn't it work?
The interviewer is checking: do you know String methods return new Strings?
Show a strong answer
- Both calls return new Strings that are thrown away;
emailis unchanged. - Fix:
email = email.trim().toLowerCase(); - Normalise the same way at sign-up and at login, so stored and typed values always match.
- Add a unit test with spaces and mixed case.
Avoid: “The database is case-sensitive, change the database.” The code bug is the real cause.
The CSV export that takes minutes
A transaction export builds a CSV with csv = csv + row + "\n"; inside a loop. With 1,000 rows it's instant; with 2 lakh rows it takes minutes and memory spikes. Explain and fix.
The interviewer is checking: do you connect immutability with the cost of repeated concatenation?
Show a strong answer
Each + creates a brand-new String and copies all the text so far. Row 1 copies a little, row 2 lakh copies almost the whole file: the total copying grows roughly with n², and millions of temporary Strings stress the garbage collector.
- Use a
StringBuilderandappendeach row: it grows one buffer instead of copying everything each time. - For big exports, write rows directly to the output stream or file instead of building one giant String.
Avoid: increasing the server's memory. The algorithm is the problem.
A password showed up in a heap dump
During a production memory investigation, a heap dump was shared with a vendor. A security review found users' plain-text passwords inside it. The login code stores the password in a String before hashing. What happened, and what would you change?
The interviewer is checking: do you understand that immutable Strings can't be wiped from memory?
Show a strong answer
- A String can't be overwritten. It stays in memory until the garbage collector reclaims it, which may be much later, so a heap dump can capture it.
- Keep secrets in a
char[](orbyte[]) and overwrite it withArrays.fill(pw, '\0')right after hashing. - Hash immediately with a password-hashing algorithm (bcrypt, scrypt, Argon2), never log request bodies with passwords, and restrict who can take and share heap dumps.
Avoid: “Set the String to null.” That removes the reference, not the characters in memory.
Why String makes a safe map key
A teammate asks: “Our cache uses HashMap<String, Product> keyed by SKU. Could we use our own mutable SkuCode class with a setter instead?” What do you explain?
The interviewer is checking: do you know why immutability matters for hash-based keys?
Show a strong answer
- A HashMap puts an entry in a bucket chosen from the key's hash code. If a mutable key changes after insertion, its hash changes and
getlooks in the wrong bucket: the entry is “lost” though still in the map. - String is immutable, so its hash code never changes; Java even caches it after the first calculation, making lookups fast.
- If a custom key class is needed, make it immutable (final fields, no setters) and implement
equalsandhashCodeconsistently.
Avoid: mutable keys “because we never change them”. Someone eventually will.