String class and immutability

Names, emails, OTP messages, invoice numbers: most data a program touches is text. In Java, text is a String object, and Strings have one rule that surprises every beginner: they never change. You'll see what that means in memory, why toUpperCase() seems to “not work”, why Java chose this design, and then practise with 11 problems.

Module 4 · Arrays and Strings⏱ 22 minBeginner11 practice problems
1 The story

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.

name "riya" toUpperCase() "RIYA" a NEW String… but nobody is holding it The original is never changed. name still points here. myphatshala.in
Riya

Sir, toUpperCase() doesn't work! My name is still small letters.

Arjun Sir

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();.

Riya

So it can't change the original “riya”?

Arjun Sir

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.

2 Definition

What is a String?

Formal definition (write this in exams)

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.

In simple words

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

LiteralText in double quotes in your code: "riya".
ImmutableCannot be changed after it is created.
ConcatenationJoining Strings with + or concat().
charA single character, in single quotes: 'R'.
Empty String"": a real String with length 0.
nullNo String object at all. Calling a method on it throws NullPointerException.

String facts at a glance

Classjava.lang.String (imported automatically), declared final: no subclass can change its behaviour
Lengths.length() (a method, with brackets)
One characters.charAt(i) returns a char; valid i is 0 to length() - 1
Join"Hi " + name; any value joined to a String becomes text
“Changing” methodstoUpperCase, 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.

3 Types and ways to use it

Ways to create a String

Literal

String city = "Pune";
The usual way. Literals are shared from the String pool (next lesson).

Most common

new String(…)

new String("Pune") or new String(charArray).
Always creates a fresh object.

Rarely needed

From other values

String.valueOf(42), "" + 3.5, Integer.toString(7).
Numbers and booleans to text.

Conversion

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");
    }
}
3345 Total: 1020 Total: 30 131C

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?

ReasonWhat it gives you
Sharing (String pool)Thousands of variables can safely share one "Pune" object, saving memory.
SecurityA file path, URL or username checked for safety can't be changed afterwards by other code.
Thread safetyMany threads can read the same String with no locks.
Fast HashMap keysThe 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).

4 Step by step

What really happens when you “change” a String

  1. Create

    String name = "riya";
    A String object holding r-i-y-a exists on the heap; the variable name holds its address.
  2. Call a “changing” method

    name.toUpperCase();
    The method reads the old characters, creates a new String “RIYA”, and returns its address. Nobody stores it, so it becomes garbage.
  3. Catch the result

    name = name.toUpperCase();
    Now the variable name is updated to point to the new object. The variable changed; the old String did not.
  4. Read characters safely

    for (int i = 0; i < name.length(); i++) {
        System.out.println(name.charAt(i));
    }
    charAt is read-only. There is no setCharAt on String; name.charAt(0) = 'r' doesn't even compile.
  5. Watch out for null

    String s = null; s.length(); compiles but throws NullPointerException. "" is safe: its length is 0.

    null means the variable refers to no object, so there is nothing to call length() on.
5 See it happen

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.

Immutability in memory 6 steps
What you see String name = "riya"; name.toUpperCase(); name = name.toUpperCase(); String other = name; name = name + " S"; name.charAt(0) = 'X'; // ? name → riya name → riya (unchanged!) name → RIYA name → RIYA other → RIYA name → RIYA S other → RIYA (still) error: unexpected type required: variable found: value A String with four characters. The result was thrown away. Now the variable has the new String. Two names, one String. name moved; other did not. You can't write into a String. myphatshala.in
Behind the screen Stack Heap name other "riya" "RIYA" "RIYA S" garbage? final byte[] name holds the address of "riya". New object "RIYA" made, but no variable holds it. The arrow moved. "riya" itself is untouched. other copies the address: same object. + made a third object; only name moved to it. Each String keeps its chars in a private final array. myphatshala.in
6 Real-world use

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).

Checking "Riya@2025" with charAt() 6 steps
Animated example R 0 i 1 y 2 a 3 @ 4 2 5 0 6 2 7 5 8 length() = 9 i length ≥ 8 ✓ 9 has capital ✓ 'R' at 0 has digit ✓ '2' at 5 has capital has digit '@' is neither STRONG ✓ pw itself never changed: Strings are read, not edited myphatshala.in

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.

" Riya@Mail.com "→trim().toLowerCase()→"riya@mail.com"
OTP 482913to XXXXXX3210

2. Messages and notifications

SMS, emails and push notifications are built by joining fixed text with values: names, amounts, masked numbers, links.

Template + values→"Hi " + name + …→SMS text
"user42"profile

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.

"SKU-1042"→map.get(key)→Product

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.

Path→check, then open→Same path guaranteed

Examples are simplified to show where String behaviour matters.

7 Run the code

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));
    }
}
Oops: riya sharma Badge: RIYA SHARMA Letters on badge: 11 Initial: R
  • name.toUpperCase();Creates “RIYA SHARMA” and throws it away. name is 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 a char, here 'R'.
8 Line by line

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.

Example 1Events · registration desk

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.

Input
raw = "  riya  "
Expected output
[RIYA]
[  riya  ]

Approach

  1. Call trim() and toUpperCase(): each returns a new String.
  2. Return the final String from the method; the caller stores it in a new variable.
  3. 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.

Example 2Warehouse · labels

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.

Input
n = 3
Expected output
SKU-1|SKU-2|SKU-3

Approach

  1. Start with an empty String.
  2. In each loop pass, add a separator (except the first time) and the next code.
  3. 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.

Example 3Banking · customer letters

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.

Input
name = "Arjun"
Expected output
Broken: Arjun
Fixed:  Mr. Arjun

Approach

  1. The method receives a copy of the reference. Reassigning that copy inside the method doesn't touch the caller's variable.
  2. Strings can't be changed in place, so the only way to give back a new name is to return it.
  3. 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.

9 Quiz and revision

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 to length() - 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.

P1Easy

Count the vowels

Count how many vowels (a, e, i, o, u, either case) a sentence has.

Input
text = "Java Is Fun To Learn"
Expected output
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);
    }
}
Vowels: 7

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.

P2Easy

Reverse a string

Print a word backwards.

Input
word = "Phatshala"
Expected output
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);
    }
}
alahstahP

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).

P3Easy

Palindrome check

Check whether a word reads the same forwards and backwards, ignoring case.

Input
word = "Malayalam"
Expected output
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");
    }
}
Malayalam is 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.

P4Easy

Letters, digits and spaces

Count letters, digits, spaces and other characters in an address line.

Input
addr = "Flat 12B, MG Road 560001"
Expected output
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);
    }
}
Letters 11, digits 8, spaces 4, others 1

How it works: the Character class has helpers like isLetter and isDigit. An if-else-if ladder puts each character in exactly one bucket.

P5Medium

Toggle case

Change upper case letters to lower case and lower to upper. Leave other characters alone.

Input
s = "HeLLo World 2026"
Expected output
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);
    }
}
hEllO wORLD 2026

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.

P6Medium

How many times does a character appear?

Count how many times a given character appears in a string, and list the positions.

Input
s = "banana", target = 'a'
Expected output
'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());
    }
}
'a' appears 3 times at index 1 3 5

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.

P7Medium

Anagram check

Two words are anagrams if they use exactly the same letters the same number of times. Check “listen” and “silent”.

Input
a = "listen", b = "silent"
Expected output
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");
    }
}
listen and silent are 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.

P8Medium

Capitalise each word

Make the first letter of every word upper case and the rest lower case.

Input
name = "rIYA sHARMA kumar"
Expected output
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);
    }
}
Riya Sharma Kumar

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.

P9Business problem

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.

Input
mobile = "9876543210"
Expected output
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);
    }
}
OTP sent to XXXXXX3210

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.

P10Business problem

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.

Input
"riya_99", "9lives", "ab", "hello world", "Kabir_Dev"
Expected output
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));
        }
    }
}
riya_99 -> OK 9lives -> must start with a letter ab -> must be 5-15 characters hello world -> invalid character ' ' Kabir_Dev -> OK

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.

P11Business problem

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.

Input
year = 2026, serials = 7, 42, 1350, 98765
Expected output
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);
        }
    }
}
INV-2026-00007 INV-2026-00042 INV-2026-01350 INV-2026-98765

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.

E-commerce · loginFresher

“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; email is 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.

Fintech · reports1–3 years

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 StringBuilder and append each 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.

Security · credentials1–3 years

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[] (or byte[]) and overwrite it with Arrays.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.

Backend · caching1–3 years

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 get looks 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 equals and hashCode consistently.

Avoid: mutable keys “because we never change them”. Someone eventually will.