</>SyntaxPilot
Notes › Java › Episode 9

Java Strings: Immutability, the String Pool, equals vs == and String Methods

▶ Watch the videoTry the code in the compilerAll Java notes

These are the notes for Java episode 9. You will learn why name.toUpperCase(); on its own changes nothing (Strings are immutable), the string pool and why two literals are the same object, why == rejects the right username while equals and equalsIgnoreCase work, how to read a String with length(), charAt, indexOf and substring, the StringIndexOutOfBoundsException, and how to clean messy input with strip, replace and split. Every example below runs as-is in the SyntaxPilot compiler, with its real output from javac 21.

The problem: a registration desk full of messy names

At a college fest, volunteers type every visitor's name in a hurry: " priya SHARMA ". Extra spaces, mixed case. The badge printer needs clean capitals, and the volunteer login needs to compare two pieces of text. Both jobs have a trap.

Strings are immutable

A String is Java's type for text. It is an object with methods (things it can do), such as toUpperCase(). The obvious attempt:

public class Main {
    public static void main(String[] args) {
        String name = "priya";
        name.toUpperCase();
        System.out.println(name);
    }
}
priya

Nothing changed, with no error and no warning.

Why: a String can never change after it's created. toUpperCase() doesn't touch the original; it builds a new String and returns it. We didn't store the new String, so it was thrown away.

Real-life example: a printed certificate.

At the print shop In Java
once printed, the ink is final a String never changes: it is immutable
a spelling fix means printing a new certificate every String method returns a new String
put the new one in your file, or it's lost store the result: name = name.toUpperCase();
public class Main {
    public static void main(String[] args) {
        String name = "priya";
        String loud = name.toUpperCase();
        System.out.println(name + " / " + loud);
        name = name.toUpperCase();
        System.out.println(name);
    }
}
priya / PRIYA
PRIYA

name = name.toUpperCase(); doesn't change the old String either: it makes the variable name point to the new one. The old "priya" is simply forgotten (and later cleaned up by Java).

The login that fails: == on Strings

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String saved = "Rahul";
        String typed = sc.next();
        if (typed == saved) {
            System.out.println("Welcome, " + typed);
        } else {
            System.out.println("Access denied");
        }
    }
}

Input Rahul:

Access denied

The exact right name, rejected. To see why, we need to know where Strings live.

The string pool

Real-life example: a school library.

In a school library In Java
ask for a book by title: everyone shares the one copy on the shelf identical string literals share one object in the string pool
buy your own copy: same words, a different book new String("Rahul") or text read from the keyboard is a new object
"is it the same book?" vs "does it say the same thing?" == vs equals

A string literal is text written in double quotes in your code ("Rahul"). Java keeps literals in a special area of memory called the string pool and reuses one object for every identical literal.

public class Main {
    public static void main(String[] args) {
        String a = "Rahul";
        String b = "Rahul";
        String c = new String("Rahul");
        System.out.println(a == b);
        System.out.println(a == c);
        System.out.println(a.equals(c));
    }
}
true
false
true
 variables         string pool              heap (outside the pool)
   a ───────┐
            ├──► "Rahul"
   b ───────┘
   c ─────────────────────────────────────►  "Rahul"  (a separate object)

That's why the login failed: saved is a pooled literal, typed is a new object built from keyboard input. Same letters, different objects.

Immutability and the pool go together: because many variables can share one pooled String, it must never change. If it could, changing it through one variable would change it for every other variable too.

The fix: equals and equalsIgnoreCase

Replace typed == saved with typed.equals(saved):

Input == equals equalsIgnoreCase
Rahul Access denied Welcome, Rahul Welcome, Rahul
rahul Access denied Access denied Welcome, rahul

equals is strict: r and R are different characters. equalsIgnoreCase compares the letters but ignores capital vs small. Use equals for passwords (case matters) and equalsIgnoreCase for names and usernames.

Tip: when one side is a literal, write it first: "Rahul".equals(typed). It works even if typed is null.

Reading a String: length(), charAt, indexOf, substring

A String is a row of characters numbered from 0, like an array.

 P   r   i   y   a
 0   1   2   3   4        length() = 5
Method Returns "Priya" example
length() number of characters (a method: brackets!) 5
charAt(i) the char at index i charAt(0) → 'P'
indexOf(x) the first index of x, or -1 if not found indexOf('y') → 3
substring(a, b) characters from a up to but not including b substring(1, 3) → "ri"
substring(a) from a to the end substring(2) → "iya"
public class Main {
    public static void main(String[] args) {
        String email = "priya.sharma@gmail.com";
        System.out.println(email.length());
        System.out.println(email.charAt(0));
        int at = email.indexOf('@');
        System.out.println(at);
        System.out.println(email.substring(0, at));
        System.out.println(email.substring(at + 1));
    }
}
22
p
12
priya.sharma
gmail.com

Going past the end

String name = "Priya";
System.out.println(name.charAt(5));
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
    ...
    at Main.main(Main.java:4)

The last character is at length() - 1.

Asking questions about a String

public class Main {
    public static void main(String[] args) {
        String email = "priya.sharma@gmail.com";
        System.out.println(email.contains("@"));
        System.out.println(email.endsWith(".com"));
        System.out.println(email.startsWith("rahul"));
        System.out.println(email.indexOf('#'));
    }
}
true
true
false
-1

indexOf doesn't crash when it finds nothing: it returns -1, meaning "not found".

Cleaning input: strip, replace, split

public class Main {
    public static void main(String[] args) {
        String raw = "   Priya  Sharma ";
        System.out.println("[" + raw + "]");
        System.out.println("[" + raw.strip() + "]");
        System.out.println(raw.strip().replace("  ", " "));
        String[] parts = "Priya Sharma".split(" ");
        System.out.println(parts[0] + " | " + parts[1] + " | " + parts.length);
    }
}
[   Priya  Sharma ]
[Priya  Sharma]
Priya Sharma
Priya | Sharma | 2

Other useful methods: toLowerCase(), isEmpty(), isBlank(), lastIndexOf(x), repeat(n), compareTo(other) (dictionary order: negative, 0 or positive).

Common mistakes (and the real error messages)

Mistake Real message Why Fix
name.toUpperCase(); no error, name unchanged Strings are immutable name = name.toUpperCase();
if (typed == saved) no error, wrong answer == compares objects typed.equals(saved)
name.charAt(5) on "Priya" StringIndexOutOfBoundsException: Index 5 out of bounds for length 5 indexes go 0 to length() - 1 name.charAt(name.length() - 1)
name.length cannot find symbol symbol: variable length location: variable name of type String for a String, length() is a method name.length()
String s = 'Priya'; unclosed character literal single quotes hold one char "Priya"
"Priya".substring(3, 1) StringIndexOutOfBoundsException: Range [3, 1) out of bounds for length 5 start must be ≤ end substring(1, 3)

Mini project: username maker

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("Full name?");
        String full = sc.nextLine().strip().toLowerCase();
        String[] parts = full.split(" ");
        String first = parts[0];
        String last = parts[parts.length - 1];
        String user = first.charAt(0) + last;
        String nice = first.substring(0, 1).toUpperCase() + first.substring(1);
        System.out.println("Hi " + nice + "! Your username: " + user);
        System.out.println("Letters in name: " + full.replace(" ", "").length());
    }
}

Input priya SHARMA:

Full name?
Hi Priya! Your username: psharma
Letters in name: 11

nextLine() reads the whole line, spaces included (next() stops at the first space). first.charAt(0) + last works because one side is a String, so + joins. Extend it: if the name has a middle name, add its first letter too (priya k sharma → pksharma).

The next problem

Building one long String in a loop (s = s + i; tens of thousands of times) creates a new String every round and copies all the old characters into it: very slow. Episode 10 times it and fixes it with StringBuilder.

Practice

  1. Count the vowels in a word.
  2. Check whether a word is a palindrome (madam).
  3. Print a name's initials: Priya Sharma → P.S.
  4. Hide an email: priya@gmail.com → p****@gmail.com.
  5. Count how many times the letter a appears in a sentence.
Solutions and hints
  1. java String w = "education"; int v = 0; for (int i = 0; i < w.length(); i++) { if ("aeiou".indexOf(w.charAt(i)) != -1) { v++; } } System.out.println(v); // 5
  2. java String w = "madam"; boolean pal = true; for (int i = 0; i < w.length() / 2; i++) { if (w.charAt(i) != w.charAt(w.length() - 1 - i)) { pal = false; } } System.out.println(pal); // true (Comparing two chars with == is fine: char is a primitive, not an object.)
  3. String[] p = "Priya Sharma".split(" "); System.out.println(p[0].charAt(0) + "." + p[1].charAt(0) + "."); → P.S.
  4. java String e = "priya@gmail.com"; int at = e.indexOf('@'); System.out.println(e.charAt(0) + "*".repeat(at - 1) + e.substring(at)); // p****@gmail.com
  5. Loop with charAt and count == 'a', or s.length() - s.replace("a", "").length().

Quick quiz

What does this print?

String s = "java";
s.toUpperCase();
System.out.println(s.substring(1, 3) + s.length());
Answer

B. av4. s.toUpperCase() returns a new String that nobody stores, so s is still "java". substring(1, 3) is index 1 and 2: "av". length() is 4, and String + int joins: av4.

Interview questions

  1. Why are Strings immutable in Java? So pooled literals can be safely shared, for security (file names, URLs, class names can't be changed after checks), thread safety, and so the hash code can be cached (useful in HashMap).
  2. What is the string pool? An area of the heap where Java keeps one shared object per distinct string literal.
  3. Difference between == and equals for Strings? == compares references; equals compares characters.
  4. How many objects does new String("Rahul") create? Up to two: the literal "Rahul" in the pool (if not already there) and a new object on the heap.
  5. What does intern() do? Returns the pooled copy of a String, adding it to the pool if needed: new String("Rahul").intern() == "Rahul" is true.
  6. trim() vs strip()? Both remove leading and trailing whitespace; strip() (Java 11) also handles Unicode whitespace.
  7. What does indexOf return when nothing is found? -1.

Glossary

Term Meaning Example
String Java's type for text; an object "Priya"
object a value that has data and methods a String
method something an object can do, called with a dot and brackets name.toUpperCase()
immutable can never change after it's created every String
string literal text written in double quotes in the code "Rahul"
string pool the memory area where identical literals share one object "Rahul" == "Rahul" → true
reference a variable's link to an object; == compares these a == c → false
equals compares two Strings' characters a.equals(c) → true
equalsIgnoreCase compares characters, ignoring capital vs small "rahul" vs "Rahul" → true
char one character, in single quotes 'P'
index a character's position, from 0 charAt(0)
substring a piece cut out of a String substring(1, 3) → "ri"
whitespace spaces, tabs and new lines " Priya "
strip / trim remove whitespace at both ends " a ".strip() → "a"
split cut a String into a String[] at a separator "a b".split(" ")
chaining calling a method on the result of another s.strip().toLowerCase()
StringIndexOutOfBoundsException the runtime crash for an index outside 0..length()-1 "Priya".charAt(5)
nextLine() Scanner method that reads a whole line, spaces included sc.nextLine()

Summary

Next episode: StringBuilder and StringBuffer: text you can change, and a live race against String.

← Episode 8Java 2D Arrays, Jagged Arrays and the java.util.Arrays Toolbox