JavaBasicsbeginner
Updated:

Java Strings: String Pool, Immutability and Key Methods

6 min read

Why Java strings are immutable, how the string pool saves memory, why == fails where equals() works, and when StringBuilder beats String — with runnable examples.

TL;DR – Quick Answer

A Java String is an immutable object: once created, its characters can never change, and every 'modifying' method returns a new String. String literals are cached in the string pool inside the heap so identical literals share one object. Compare string contents with equals(), never ==, and use StringBuilder when you build strings in loops.

On This Page

Strings look deceptively simple — you have used them since your first System.out.println("Hello"). But String is also the class behind the most repeated Java interview questions ever asked: why is String immutable, what is the string pool, why does == sometimes work and sometimes fail? Understanding what actually happens in memory turns all of those from memorized trivia into answers you can derive on the spot.

A String is not a primitive like int from the data types table — it is an object: internally a sequence of characters wrapped in a class packed with useful methods.

Creating strings: literal vs new

There are two ways to create a string, and they behave differently in memory:

String a = "codebegun";                 // literal — goes to the string pool
String b = "codebegun";                 // reuses the SAME pooled object
String c = new String("codebegun");     // new object on the heap, outside the pool

System.out.println(a == b);             // true  — same object
System.out.println(a == c);             // false — different objects
System.out.println(a.equals(c));        // true  — same characters

With a literal, the JVM first checks the string pool — a cache of unique string objects inside the heap. "codebegun" already exists when b is assigned, so b simply points to the same object as a. With new, Java is forced to create a distinct object regardless of the pool, which is why a == c is false.

This is why new String("...") is considered a code smell: it defeats the pool's memory sharing for no benefit. Where exactly the pool and heap live is covered in Java memory management.

Common mistake: A common mistake beginners make is comparing strings with ==. It appears to work in small test programs because literals share pooled objects — then fails in real code the moment a string arrives from user input, a file or a database, because those strings are not pooled. Content comparison is always equals(), or equalsIgnoreCase() when case does not matter.

Immutability: why strings never change

Every String is immutable: after creation, its characters cannot be altered. Methods that appear to modify a string actually return a brand-new one:

public class ImmutableDemo {
    public static void main(String[] args) {
        String name = "java";
        name.toUpperCase();               // result thrown away!
        System.out.println(name);         // java — unchanged

        name = name.toUpperCase();        // reassign to capture the new string
        System.out.println(name);         // JAVA
    }
}

The first toUpperCase() call did produce "JAVA" — but as a new object that was immediately discarded because nothing captured it. Forgetting to reassign is one of the top five bugs in fresher code.

Why did Java's designers choose immutability?

  • Pool safety. Dozens of variables may share one pooled object. If strings were mutable, changing one variable's text would silently change all of them.
  • Hash caching. A string's hash code is computed once and cached, making String the ideal key type for a HashMap. Mutable keys would corrupt the map.
  • Thread safety. Immutable objects can be shared across threads with no locking.
  • Security. File paths, URLs and class names are validated as strings; immutability guarantees they cannot change between validation and use.

The methods you will actually use

String has 70+ methods; these fifteen or so cover 95% of real work:

Method Example Result
length() "hello".length() 5
charAt(i) "hello".charAt(1) 'e'
substring(b, e) "hello".substring(1, 4) "ell"
indexOf(s) "hello".indexOf("ll") 2
contains(s) "hello".contains("ell") true
replace(a, b) "hello".replace('l', 'p') "heppo"
trim() / strip() " hi ".strip() "hi"
split(regex) "a,b,c".split(",") ["a", "b", "c"]
toUpperCase() "hi".toUpperCase() "HI"
startsWith(s) "hello".startsWith("he") true
isEmpty() / isBlank() " ".isBlank() true

Two details worth flagging. substring(begin, end) includes begin but excludes end — so the length of the result is always end - begin. And split returns a String array, which is how you break a CSV line into fields.

Here they are working together in a small, runnable validator:

public class EmailChecker {
    public static void main(String[] args) {
        String input = "  Ravi.Kumar@Example.COM  ";
        String email = input.strip().toLowerCase();

        boolean valid = email.contains("@")
                && email.indexOf("@") > 0
                && email.endsWith(".com");

        String username = email.substring(0, email.indexOf("@"));
        System.out.println("Email: " + email);      // ravi.kumar@example.com
        System.out.println("Valid: " + valid);      // true
        System.out.println("User:  " + username);   // ravi.kumar
    }
}

Notice the chaining in input.strip().toLowerCase(): each call returns a new string that the next call operates on. Chaining works precisely because every method returns a String.

Comparing strings properly

Three comparison tools, three different jobs:

  • equals(other) — character-by-character equality, case-sensitive.
  • equalsIgnoreCase(other) — equality ignoring case; ideal for emails and usernames.
  • compareTo(other) — lexicographic ordering; returns negative, zero or positive, which is what sorting uses.

Strings also work in switch statements (Java 7+), matching by equals() — see control statements for the syntax. One guard to remember: call equals on the value you know is not null, or use "admin".equals(role) style so a null role returns false instead of throwing.

Interview note: "How many objects does String s = new String("java"); create?" The expected answer: up to two — the pooled "java" literal, plus the heap object that new forces. Follow-up: s.intern() returns the pooled version. If you can draw the pool and heap on the whiteboard while explaining, this question is finished in a minute.

StringBuilder: when immutability becomes expensive

Immutability has a cost: every concatenation creates a new object. Inside a loop, that cost explodes:

public class ConcatBenchmark {
    public static void main(String[] args) {
        int n = 50_000;

        long t1 = System.currentTimeMillis();
        String report = "";
        for (int i = 0; i < n; i++) {
            report += i + ",";            // new String object EVERY pass
        }
        long stringMs = System.currentTimeMillis() - t1;

        long t2 = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            sb.append(i).append(',');     // modifies one internal buffer
        }
        String report2 = sb.toString();
        long builderMs = System.currentTimeMillis() - t2;

        System.out.println("String +=      : " + stringMs + " ms");
        System.out.println("StringBuilder  : " + builderMs + " ms");
    }
}

Run it: the += loop typically takes seconds while StringBuilder finishes in milliseconds, because += copies the entire accumulated string on every iteration — roughly O(n²) total work — while StringBuilder appends into a resizable internal buffer.

The rule of thumb: + is fine for joining a handful of values in one expression (the compiler optimizes that case anyway). The moment concatenation happens inside a loop, switch to StringBuilder.

StringBuffer is the legacy, synchronized twin of StringBuilder — same API, slower due to locking. Mention it in interviews as "thread-safe but rarely needed"; write StringBuilder in code.

Pro tip: StringBuilder also gives you the easiest string-reverse in Java: new StringBuilder(s).reverse().toString(). Interviewers may ask you to reverse without it — using a char array and two indexes — so practise both versions.

Formatting and joining: the underrated helpers

Three static helpers save you from clumsy concatenation in everyday code. String.format builds a string from a template: String.format("%s scored %d (%.1f%%)", name, marks, pct) reads far better than five + operations, and the same format codes work in System.out.printf. String.join glues a collection or array with a separator — String.join(", ", cities) produces "Hyderabad, Pune, Chennai" in one line, replacing the loop-plus-trailing-comma dance entirely. And String.valueOf(x) converts any value, including null, into a string without throwing.

Going the other direction — string to number — is the job of the wrapper classes: Integer.parseInt("42") and Double.parseDouble("3.14"). Both throw NumberFormatException on bad input such as "42abc" or an empty string, so any parsing of user input belongs inside a try-catch. Freshers lose easy marks in machine tests by letting unvalidated parseInt calls crash on the tester's edge-case input; a two-line catch block is usually the difference.

Strings and memory: a quick map

Pulling the pieces together: all String objects live on the heap. The string pool is a region inside the heap holding one canonical copy of each literal. Variables hold references. == compares those references; equals() follows them and compares contents. Because pooled objects are shared and strings are immutable, none of this sharing can ever produce surprising changes — that is the whole design, and once you can narrate it, most string interview questions answer themselves.

Practice before you move on

Solid drills, each doable with what this page covered: count vowels in a sentence, check whether a string is a palindrome, find the first non-repeating character, reverse the words in a sentence (not the letters), and split a CSV line and total its numeric fields. String manipulation appears in almost every fresher machine test we have seen companies run, which is why the Java Full Stack course at CodeBegun dedicates a full practice block to strings before moving into collections.

Frequently Asked Questions

Why are strings immutable in Java?
Immutability makes strings safe to share between threads, safe to use as HashMap keys because their hash code never changes, and safe for the string pool where many variables share one object. It also protects security-sensitive values like class names and file paths from being changed after validation.
What is the string pool in Java?
The string pool is a special cache inside the heap where the JVM stores string literals. When two variables are assigned the same literal, both point to the single pooled object instead of creating duplicates. Strings created with new String() live outside the pool unless you call intern().
What is the difference between == and equals() for strings?
== compares references, asking whether two variables point to the same object. equals() compares the actual characters. Two strings with identical text can live in different objects, so == may return false where equals() returns true. Always use equals() for content comparison.
What is the difference between String, StringBuilder and StringBuffer?
String is immutable. StringBuilder is mutable and fast, ideal for building strings in loops. StringBuffer is the older thread-safe version of StringBuilder; its synchronization makes it slower, and in single-threaded code you should prefer StringBuilder.
How many objects does new String("java") create?
Up to two: the literal "java" goes in the string pool (if not already there), and new creates a second object on the heap outside the pool. This is exactly why new String() is discouraged in normal code — the literal alone is enough.
Can we use strings in a switch statement?
Yes, since Java 7 switch accepts String values, and the matching is based on equals(), so it is case-sensitive. Be careful that switching on a null string throws a NullPointerException before any case is evaluated.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 14 July 2026 LinkedIn
Chat with us