Java String Comparison
Java String Comparison
String comparison is one of those topics that looks trivial until it silently breaks production code. A login system that uses == instead of equals() lets every user in — or lets no one in. A report sorted with the wrong comparator mixes "Apple" and "apple" incorrectly. A null check done in the wrong order throws NullPointerException during a critical payment flow.
Java gives you several ways to compare strings. Each one answers a different question — same object in memory, same content, same content ignoring case, or which string comes first alphabetically. Knowing which question you actually need answered determines which method to use.
The Four Questions String Comparison Answers
Question 1: Do both variables point to the SAME object in memory? → Use == Question 2: Do both strings contain exactly the SAME characters? → Use equals() Question 3: Do both strings have the same characters, regardless of case? → Use equalsIgnoreCase() Question 4: Which string comes first in alphabetical (dictionary) order? → Use compareTo() or compareToIgnoreCase()
These four questions cover every legitimate string comparison scenario. Picking the wrong one produces either a silent bug (wrong result, no exception) or a runtime crash.
1 — == Operator: Reference Comparison
== on objects checks whether two variables point to the same memory address — the same object instance. It does not check content. Two different String objects that contain identical text return false with ==.
1// File: ReferenceComparisonDemo.java
2
3public class ReferenceComparisonDemo {
4
5 public static void main(String[] args) {
6
7 // Literals — stored in the String Pool — same object reused
8 String a = "hello";
9 String b = "hello";
10 String c = "world";
11
12 System.out.println("=== String Pool (literals) ===");
13 System.out.println("a == b : " + (a == b)); // true — same pool object
14 System.out.println("a == c : " + (a == c)); // false — different pool object
15
16 System.out.println();
17
18 // new String() — always creates a new object outside the pool
19 String x = new String("hello");
20 String y = new String("hello");
21
22 System.out.println("=== Heap Objects (new keyword) ===");
23 System.out.println("a == x : " + (a == x)); // false — pool vs heap
24 System.out.println("x == y : " + (x == y)); // false — two separate heap objects
25 System.out.println("x == x : " + (x == x)); // true — same reference to itself
26
27 System.out.println();
28
29 // What == actually checks — memory address
30 System.out.println("=== Identity Hash Codes ===");
31 System.out.println("a hashCode: " + System.identityHashCode(a));
32 System.out.println("b hashCode: " + System.identityHashCode(b)); // same as a
33 System.out.println("x hashCode: " + System.identityHashCode(x)); // different
34 System.out.println("y hashCode: " + System.identityHashCode(y)); // different from x too
35 }
36}Output:
=== String Pool (literals) ===
a == b : true
a == c : false
=== Heap Objects (new keyword) ===
a == x : false
x == y : false
x == x : true
=== Identity Hash Codes ===
a hashCode: 1829164700
b hashCode: 1829164700
x hashCode: 2018699554
y hashCode: 1311053135
a and b share the same identity hash code — they are literally the same object, which is why == returns true. x and y each have a unique address — they are separate objects even though their content is identical.
Memory layout:
String Pool:
┌─────────────┐
│ "hello" │ ◄── a and b both point here
└─────────────┘
┌─────────────┐
│ "world" │ ◄── c
└─────────────┘
Heap (normal area):
┌─────────────┐ ┌─────────────┐
│ "hello" │ │ "hello" │
└─────────────┘ └─────────────┘
▲ ▲
x y
When == is appropriate for Strings: Almost never for content comparison. The only legitimate use is checking whether a string is literally the same object as another known reference — which is unusual in application code.
2 — equals(): Content Comparison
equals() compares the actual character content of two strings. It returns true when both strings contain the same sequence of characters in the same case, regardless of whether they are the same object in memory.
1// File: EqualsDemo.java
2
3public class EqualsDemo {
4
5 public static void main(String[] args) {
6
7 // equals() checks content — not memory location
8 String s1 = new String("Java");
9 String s2 = new String("Java");
10 String s3 = new String("java"); // lowercase j
11
12 System.out.println("=== equals() ===");
13 System.out.println("s1.equals(s2) : " + s1.equals(s2)); // true — same content
14 System.out.println("s1.equals(s3) : " + s1.equals(s3)); // false — 'J' != 'j'
15 System.out.println("s1 == s2 : " + (s1 == s2)); // false — different objects
16
17 System.out.println();
18
19 // equals() with null — calling on a null reference throws NPE
20 String username = null;
21
22 // RISKY — throws NullPointerException if username is null
23 // if (username.equals("admin")) { ... }
24
25 // SAFE — put the known non-null constant on the LEFT
26 System.out.println("=== Null Safety ===");
27 System.out.println("\"admin\".equals(null) : " + "admin".equals(username)); // false — no NPE
28 System.out.println("\"admin\".equals(\"\") : " + "admin".equals("")); // false
29 System.out.println("\"admin\".equals(\"admin\"): " + "admin".equals("admin")); // true
30
31 System.out.println();
32
33 // Practical use — credential validation
34 String storedPassword = "Secret@123";
35 String[] attempts = {"secret@123", "Secret@123", "SECRET@123", "", null};
36
37 System.out.println("=== Password Check ===");
38 for (String attempt : attempts) {
39 boolean match = storedPassword.equals(attempt);
40 System.out.printf(" %-15s → %s%n",
41 "'" + attempt + "'", match ? "GRANTED" : "DENIED");
42 }
43 }
44}Output:
=== equals() ===
s1.equals(s2) : true
s1.equals(s3) : false
s1 == s2 : false
=== Null Safety ===
"admin".equals(null) : false
"admin".equals("") : false
"admin".equals("admin"): true
=== Password Check ===
'secret@123' → DENIED
'Secret@123' → GRANTED
'SECRET@123' → DENIED
'' → DENIED
'null' → DENIED
The constant-on-left pattern — "knownValue".equals(variable) — is the single most important habit for String comparison. When variable is null, calling variable.equals("known") crashes with NullPointerException. Calling "known".equals(variable) safely returns false.
3 — equalsIgnoreCase(): Case-Insensitive Comparison
equalsIgnoreCase() compares content while treating uppercase and lowercase letters as identical. Useful for username lookups, search, role checking, city names, and any input where the caller's capitalisation should not matter.
1// File: EqualsIgnoreCaseDemo.java
2
3public class EqualsIgnoreCaseDemo {
4
5 public static void main(String[] args) {
6
7 String stored = "bengaluru";
8 String input1 = "Bengaluru";
9 String input2 = "BENGALURU";
10 String input3 = "BenGaLuRu";
11 String input4 = "Mumbai";
12
13 System.out.println("=== equalsIgnoreCase() ===");
14 System.out.println("stored: " + stored);
15 System.out.printf(" %-12s → %s%n", input1, stored.equalsIgnoreCase(input1)); // true
16 System.out.printf(" %-12s → %s%n", input2, stored.equalsIgnoreCase(input2)); // true
17 System.out.printf(" %-12s → %s%n", input3, stored.equalsIgnoreCase(input3)); // true
18 System.out.printf(" %-12s → %s%n", input4, stored.equalsIgnoreCase(input4)); // false
19
20 System.out.println();
21
22 // Practical use 1 — search feature (case-insensitive product name match)
23 String[] productNames = {
24 "Wireless Headphones", "USB Hub", "Laptop Stand",
25 "Mechanical Keyboard", "LED Monitor"
26 };
27 String searchQuery = "laptop stand";
28
29 System.out.println("=== Product Search: '" + searchQuery + "' ===");
30 for (String product : productNames) {
31 if (product.equalsIgnoreCase(searchQuery)) {
32 System.out.println(" Found: " + product);
33 }
34 }
35
36 System.out.println();
37
38 // Practical use 2 — role-based access control
39 String userRole = "MANAGER";
40 System.out.println("=== Role Check: " + userRole + " ===");
41
42 if ("admin".equalsIgnoreCase(userRole)) {
43 System.out.println(" Full access granted.");
44 } else if ("manager".equalsIgnoreCase(userRole)) {
45 System.out.println(" Reports and team access granted.");
46 } else if ("viewer".equalsIgnoreCase(userRole)) {
47 System.out.println(" Read-only access granted.");
48 } else {
49 System.out.println(" Unknown role — access denied.");
50 }
51
52 System.out.println();
53
54 // Difference between equals and equalsIgnoreCase
55 String p = "PRIYA";
56 String q = "priya";
57 System.out.println("equals() : " + p.equals(q)); // false
58 System.out.println("equalsIgnoreCase(): " + p.equalsIgnoreCase(q)); // true
59 }
60}Output:
=== equalsIgnoreCase() ===
stored: bengaluru
Bengaluru → true
BENGALURU → true
BenGaLuRu → true
Mumbai → false
=== Product Search: 'laptop stand' ===
Found: Laptop Stand
=== Role Check: MANAGER ===
Reports and team access granted.
=== Difference between equals and equalsIgnoreCase ===
equals() : false
equalsIgnoreCase(): true
4 — compareTo() and compareToIgnoreCase()
compareTo() returns an integer — not a boolean. Zero means equal. A negative number means the calling string comes before the argument alphabetically. A positive number means it comes after. This is used for sorting.
1// File: CompareToDemo.java
2
3import java.util.Arrays;
4
5public class CompareToDemo {
6
7 public static void main(String[] args) {
8
9 // compareTo() return values
10 String apple = "Apple";
11 String banana = "Banana";
12 String cherry = "Cherry";
13 String apple2 = "Apple";
14
15 System.out.println("=== compareTo() ===");
16 System.out.println("'Apple'.compareTo('Banana') : " + apple.compareTo(banana)); // negative — A before B
17 System.out.println("'Banana'.compareTo('Apple') : " + banana.compareTo(apple)); // positive — B after A
18 System.out.println("'Apple'.compareTo('Apple') : " + apple.compareTo(apple2)); // 0 — equal
19 System.out.println("'Apple'.compareTo('Cherry') : " + apple.compareTo(cherry)); // negative
20
21 System.out.println();
22
23 // compareTo() with case sensitivity
24 String upper = "APPLE";
25 String lower = "apple";
26 System.out.println("'APPLE'.compareTo('apple') : " + upper.compareTo(lower)); // negative — uppercase A < lowercase a in Unicode
27 System.out.println("compareToIgnoreCase : " + upper.compareToIgnoreCase(lower)); // 0
28
29 System.out.println();
30
31 // Practical use — sort an array of city names
32 String[] cities = {"Pune", "Delhi", "Bengaluru", "Mumbai", "Chennai", "Hyderabad"};
33 System.out.println("Before sort: " + Arrays.toString(cities));
34 Arrays.sort(cities); // Arrays.sort uses compareTo() internally
35 System.out.println("After sort : " + Arrays.toString(cities));
36
37 System.out.println();
38
39 // Custom sort — longer names first, then alphabetical
40 String[] names = {"Rohan", "Ananya", "Priya", "Deepak", "Sneha", "Karan"};
41 Arrays.sort(names, (a, b) -> {
42 if (a.length() != b.length()) return b.length() - a.length(); // longer first
43 return a.compareTo(b); // then alphabetical
44 });
45 System.out.println("Sorted (longer first): " + Arrays.toString(names));
46
47 System.out.println();
48
49 // compareToIgnoreCase — case-insensitive sort
50 String[] mixed = {"banana", "Apple", "cherry", "DATE", "elderberry"};
51 Arrays.sort(mixed, String::compareToIgnoreCase);
52 System.out.println("Case-insensitive sort: " + Arrays.toString(mixed));
53 }
54}Output:
=== compareTo() ===
'Apple'.compareTo('Banana') : -1
'Banana'.compareTo('Apple') : 1
'Apple'.compareTo('Apple') : 0
'Apple'.compareTo('Cherry') : -2
=== compareTo() with case ===
'APPLE'.compareTo('apple') : -32
compareToIgnoreCase : 0
=== Sorting Cities ===
Before sort: [Pune, Delhi, Bengaluru, Mumbai, Chennai, Hyderabad]
After sort : [Bengaluru, Chennai, Delhi, Hyderabad, Mumbai, Pune]
Sorted (longer first): [Deepak, Ananya, Rohan, Priya, Sneha, Karan]
Case-insensitive sort: [Apple, banana, cherry, DATE, elderberry]
compareTo() uses Unicode values. Uppercase letters ('A' = 65) come before lowercase ('a' = 97), so "APPLE".compareTo("apple") returns a negative value. Use compareToIgnoreCase() when case should not affect sort order.
5 — String Pool and How It Affects ==
Understanding the String pool explains why == sometimes returns true for strings that are "different" variables — and why relying on this is dangerous.
1// File: StringPoolDemo.java
2
3public class StringPoolDemo {
4
5 public static void main(String[] args) {
6
7 // Compile-time constants — JVM interns them to the pool
8 String a = "hello";
9 String b = "hel" + "lo"; // compile-time concatenation — same pool object
10
11 System.out.println("=== Compile-time Constants ===");
12 System.out.println("a == b : " + (a == b)); // true — both in pool
13
14 String prefix = "hel";
15 String c = prefix + "lo"; // runtime concatenation — new heap object
16
17 System.out.println("a == c : " + (a == c)); // false — c is on heap
18
19 System.out.println();
20
21 // intern() — force a string into the pool
22 String d = new String("hello");
23 String e = d.intern(); // returns the pooled version
24
25 System.out.println("=== intern() ===");
26 System.out.println("a == d : " + (a == d)); // false — d is on heap
27 System.out.println("a == e : " + (a == e)); // true — e is the pool object
28 System.out.println("a.equals(d) : " + a.equals(d)); // true — same content
29
30 System.out.println();
31
32 // Why you should NEVER rely on == for string comparison
33 String userInput = getUserInput(); // returns new String("admin") from user
34 String adminRole = "admin";
35
36 boolean wrongCheck = (userInput == adminRole); // false — different objects
37 boolean rightCheck = adminRole.equals(userInput); // true — same content
38
39 System.out.println("=== Why == Fails in Practice ===");
40 System.out.println("Wrong (==) : " + wrongCheck); // false — bug!
41 System.out.println("Correct (equals): " + rightCheck); // true
42 }
43
44 // Simulates getting a string from user input, database, or API
45 // These always create new String objects — never from the pool
46 static String getUserInput() {
47 return new String("admin");
48 }
49}Output:
=== Compile-time Constants ===
a == b : true
a == c : false
=== intern() ===
a == d : false
a == e : true
a.equals(d) : true
=== Why == Fails in Practice ===
Wrong (==) : false
Correct (equals): true
Strings from user input, file reading, database queries, and API responses are always heap objects — never from the pool. Using == to check such strings almost always returns false even when the content is identical. This is the bug that makes == vs equals() such a critical interview topic.
equals() vs == vs equalsIgnoreCase() vs compareTo() — Comparison Table
| Aspect | == | equals() | equalsIgnoreCase() | compareTo() |
|---|---|---|---|---|
| What it checks | Memory address (reference) | Character content, case-sensitive | Character content, case-insensitive | Alphabetical order |
| Return type | boolean | boolean | boolean | int (0, positive, negative) |
| Case sensitive | N/A | Yes | No | Yes |
| Null safe | Yes — null == null is true | No — NPE if called on null | No — NPE if called on null | No — NPE if called on null |
| Works on primitives | Yes | No — method, needs objects | No | No |
| Used for sorting | No | No | No | Yes — used in Arrays.sort |
| When to use | Never for content; identity checks only | Content equality (case matters) | Content equality (case irrelevant) | Sorting, ordering, finding min/max |
| Fails silently | Yes — returns false instead of crashing | No — but NPE risk on null | No — but NPE risk on null | No — but NPE risk on null |
| Pool-aware | Yes — literals may share address | No — checks characters, not address | No | No |
| Safe pattern | Avoid for Strings | "constant".equals(var) | "constant".equalsIgnoreCase(var) | Check for null first |
Null-Safe String Comparison
Null is one of the most common sources of NullPointerException in Java. All String comparison methods throw NPE when called on a null reference. There are several patterns to handle this safely.
1// File: NullSafeComparisonDemo.java
2
3import java.util.Objects;
4
5public class NullSafeComparisonDemo {
6
7 public static void main(String[] args) {
8
9 String known = "admin";
10 String maybeNull = null;
11 String alsoNull = null;
12
13 // Pattern 1 — constant on the left (most common)
14 System.out.println("=== Pattern 1: Constant on Left ===");
15 System.out.println(known.equals(maybeNull)); // false — no NPE
16 System.out.println("admin".equals(maybeNull)); // false — no NPE
17 System.out.println("admin".equalsIgnoreCase(null)); // false — no NPE
18
19 System.out.println();
20
21 // Pattern 2 — explicit null check
22 System.out.println("=== Pattern 2: Explicit Null Check ===");
23 String input = null;
24 if (input != null && input.equals("admin")) {
25 System.out.println("Match");
26 } else {
27 System.out.println("No match (null or different)");
28 }
29
30 System.out.println();
31
32 // Pattern 3 — Objects.equals() — null-safe on BOTH sides
33 System.out.println("=== Pattern 3: Objects.equals() ===");
34 System.out.println(Objects.equals(maybeNull, alsoNull)); // true — both null
35 System.out.println(Objects.equals(known, maybeNull)); // false — one is null
36 System.out.println(Objects.equals(known, "admin")); // true — same content
37 System.out.println(Objects.equals(null, null)); // true
38
39 System.out.println();
40
41 // Pattern 4 — Optional (Java 8+)
42 System.out.println("=== Pattern 4: String.valueOf() ===");
43 String value = null;
44 String safe = String.valueOf(value); // "null" string if null
45 System.out.println("safe equals 'null': " + "null".equals(safe)); // true — converts null to "null" string
46 System.out.println("safe value: '" + safe + "'");
47
48 System.out.println();
49
50 // Comparison of null vs empty vs blank
51 String[] cases = {null, "", " ", "hello"};
52 System.out.println("=== Null / Empty / Blank Classification ===");
53 for (String s : cases) {
54 String label = s == null ? "NULL"
55 : s.isEmpty() ? "EMPTY"
56 : s.isBlank() ? "BLANK (whitespace)"
57 : "HAS CONTENT";
58 System.out.printf(" %-10s → %s%n", "'" + s + "'", label);
59 }
60 }
61}Output:
=== Pattern 1: Constant on Left ===
false
false
false
=== Pattern 2: Explicit Null Check ===
No match (null or different)
=== Pattern 3: Objects.equals() ===
true
false
true
true
=== Pattern 4: String.valueOf() ===
safe equals 'null': true
safe value: 'null'
=== Null / Empty / Blank Classification ===
'null' → NULL
'' → EMPTY
' ' → BLANK (whitespace)
'hello' → HAS CONTENT
Objects.equals(a, b) is the cleanest null-safe comparison for both sides. It handles: both null → true, one null → false, both non-null → delegates to a.equals(b).
Real-World Example — Login and Search System
The Business Problem
A developer portal at a company like Razorpay or CRED needs three types of string comparison: exact password matching (case-sensitive equals()), role checking from various admin tools (case-insensitive equalsIgnoreCase()), and alphabetical listing of registered developers (using compareTo()). All three must handle null input safely.
1// File: AuthService.java
2
3import java.util.Objects;
4
5public class AuthService {
6
7 // Validates login — exact match required, null safe
8 public static String validateLogin(String inputUsername,
9 String inputPassword,
10 String storedUsername,
11 String storedPassword) {
12
13 // Use Objects.equals for null safety on both username and password
14 if (!Objects.equals(inputUsername, storedUsername)) {
15 return "FAILED: Username not found.";
16 }
17 // Password check — constant on left for null safety, exact case match
18 if (!storedPassword.equals(inputPassword)) {
19 return "FAILED: Incorrect password.";
20 }
21 return "SUCCESS: Welcome, " + storedUsername + "!";
22 }
23
24 // Checks access permission — case-insensitive role match
25 public static String getPermissions(String role) {
26 if (role == null || role.isBlank()) {
27 return "DENIED: No role assigned.";
28 }
29 if ("admin".equalsIgnoreCase(role)) {
30 return "Full access: all endpoints, all data.";
31 }
32 if ("developer".equalsIgnoreCase(role)) {
33 return "API access: read/write to own projects.";
34 }
35 if ("viewer".equalsIgnoreCase(role)) {
36 return "Read-only: dashboards and reports.";
37 }
38 return "DENIED: Unknown role '" + role + "'.";
39 }
40}1// File: DeveloperDirectory.java
2
3import java.util.Arrays;
4
5public class DeveloperDirectory {
6
7 private static final String[] DEVELOPERS = {
8 "Sneha Rao", "Karan Singh", "Priya Sharma",
9 "Rohan Mehta", "Ananya Iyer", "Deepak Joshi"
10 };
11
12 // Sort alphabetically by last name
13 public static void listByLastName() {
14 String[] sorted = Arrays.copyOf(DEVELOPERS, DEVELOPERS.length);
15 Arrays.sort(sorted, (a, b) -> {
16 String lastA = a.contains(" ") ? a.substring(a.lastIndexOf(' ') + 1) : a;
17 String lastB = b.contains(" ") ? b.substring(b.lastIndexOf(' ') + 1) : b;
18 return lastA.compareToIgnoreCase(lastB);
19 });
20
21 System.out.println("Developers (sorted by last name):");
22 for (int i = 0; i < sorted.length; i++) {
23 System.out.printf(" %d. %s%n", i + 1, sorted[i]);
24 }
25 }
26
27 // Find a developer — case-insensitive partial match
28 public static void search(String query) {
29 System.out.println("\nSearch results for '" + query + "':");
30 boolean found = false;
31
32 for (String dev : DEVELOPERS) {
33 // equalsIgnoreCase for exact name, or contains with lowercase for partial
34 if (dev.equalsIgnoreCase(query) ||
35 dev.toLowerCase().contains(query.toLowerCase())) {
36 System.out.println(" Found: " + dev);
37 found = true;
38 }
39 }
40 if (!found) {
41 System.out.println(" No results.");
42 }
43 }
44}1// File: LoginSearchDemo.java
2
3public class LoginSearchDemo {
4
5 public static void main(String[] args) {
6
7 System.out.println("╔══════════════════════════════════════════╗");
8 System.out.println("║ DEVELOPER PORTAL DEMO ║");
9 System.out.println("╚══════════════════════════════════════════╝\n");
10
11 // Login attempts
12 System.out.println("=== Login Attempts ===");
13 String[][] loginAttempts = {
14 {"priya", "Secret@123"}, // wrong username case — exact match
15 {"Priya", "Secret@123"}, // correct
16 {"Priya", "secret@123"}, // wrong password case
17 {null, "Secret@123"}, // null username
18 {"Priya", null}, // null password
19 };
20
21 for (String[] attempt : loginAttempts) {
22 System.out.println(AuthService.validateLogin(
23 attempt[0], attempt[1], "Priya", "Secret@123"));
24 }
25
26 System.out.println();
27
28 // Role-based permissions
29 System.out.println("=== Role Permissions ===");
30 String[] roles = {"ADMIN", "developer", "Viewer", "GUEST", null, ""};
31 for (String role : roles) {
32 System.out.printf(" %-12s → %s%n",
33 "'" + role + "'",
34 AuthService.getPermissions(role));
35 }
36
37 System.out.println();
38
39 // Developer directory
40 DeveloperDirectory.listByLastName();
41 DeveloperDirectory.search("priya");
42 DeveloperDirectory.search("RAO");
43 DeveloperDirectory.search("kumar"); // not found
44 }
45}Output:
╔══════════════════════════════════════════╗
║ DEVELOPER PORTAL DEMO ║
╚══════════════════════════════════════════╝
=== Login Attempts ===
FAILED: Username not found.
SUCCESS: Welcome, Priya!
FAILED: Incorrect password.
FAILED: Username not found.
FAILED: Incorrect password.
=== Role Permissions ===
'ADMIN' → Full access: all endpoints, all data.
'developer' → API access: read/write to own projects.
'Viewer' → Read-only: dashboards and reports.
'GUEST' → DENIED: Unknown role 'GUEST'.
'null' → DENIED: No role assigned.
'' → DENIED: No role assigned.
Developers (sorted by last name):
1. Ananya Iyer
2. Deepak Joshi
3. Rohan Mehta
4. Priya Sharma
5. Karan Singh
6. Sneha Rao
Search results for 'priya':
Found: Priya Sharma
Search results for 'RAO':
Found: Sneha Rao
Search results for 'kumar':
No results.
Three comparison methods, three different business needs: Objects.equals() for safe exact matching, equalsIgnoreCase() for role lookup, compareToIgnoreCase() for alphabetical sorting by last name.
Best Practices
Always use equals() for String content comparison — never ==. Use == only when you intentionally need to check whether two references point to the same object, which is rare in application code. For all content comparisons — login checks, search, validation — equals() is the correct tool.
Put the known non-null constant on the left side of equals(). Writing "expectedValue".equals(userInput) means the comparison safely returns false even when userInput is null. Writing userInput.equals("expectedValue") crashes with NullPointerException when userInput is null.
Use Objects.equals(a, b) when both sides might be null. This utility method from java.util.Objects handles all null combinations without any explicit null check — both null returns true, one null returns false, both non-null delegates to a.equals(b).
Use equalsIgnoreCase() when the caller's capitalisation should not matter. City names, product categories, role names, search queries, and user-supplied codes are all cases where the caller may type in any case. equalsIgnoreCase() handles all variations without requiring toLowerCase() on both sides.
Common Mistakes
Mistake 1 — Using == for Content Comparison
1String city1 = new String("Mumbai");
2String city2 = new String("Mumbai");
3
4if (city1 == city2) {
5 System.out.println("Same city"); // NEVER prints — different heap objects
6}
7
8// Correct
9if (city1.equals(city2)) {
10 System.out.println("Same city"); // prints correctly
11}This bug is hard to spot because it sometimes works — when both strings happen to be literals from the pool. It breaks when strings come from user input, files, or databases.
Mistake 2 — NPE From Calling equals() on a Possibly-Null Variable
1String userRole = getUserRoleFromDatabase(); // might return null
2
3// Throws NullPointerException if userRole is null
4if (userRole.equals("admin")) { // NPE here when null
5 grantAccess();
6}
7
8// Fix — constant on left
9if ("admin".equals(userRole)) { // returns false if null — no NPE
10 grantAccess();
11}
12
13// Or — Objects.equals for null-safe symmetric comparison
14if (Objects.equals(userRole, "admin")) {
15 grantAccess();
16}Mistake 3 — Using equals() Instead of equalsIgnoreCase() for User Input
1String statusFromUser = "Active"; // user might type "ACTIVE", "active", "Active"
2String storedStatus = "active";
3
4// equals() fails for differently-cased inputs
5if (storedStatus.equals(statusFromUser)) { // false — 'A' != 'a'
6 System.out.println("User is active"); // never prints
7}
8
9// Fix
10if (storedStatus.equalsIgnoreCase(statusFromUser)) { // true — case ignored
11 System.out.println("User is active");
12}Mistake 4 — Misreading compareTo() Return Value
1String a = "Mango";
2String b = "Apple";
3
4int result = a.compareTo(b); // positive — M comes after A
5
6// Wrong interpretation
7if (result == 1) {
8 System.out.println("Mango comes after Apple"); // might not always be exactly 1
9}
10
11// Correct interpretation — check sign, not specific value
12if (result > 0) {
13 System.out.println("Mango comes after Apple"); // correct
14} else if (result < 0) {
15 System.out.println("Mango comes before Apple");
16} else {
17 System.out.println("Equal");
18}compareTo() does not guarantee returning exactly 1 or -1. It returns any positive or negative number. Always use > 0, < 0, and == 0 — never == 1 or == -1.
Interview Questions
Q1. What is the difference between == and equals() for String comparison in Java?
== compares references — it checks whether two variables point to the same object in memory. equals() compares content — it checks whether two strings contain the same sequence of characters. Two different String objects with identical content return false for == but true for equals(). For string content comparison, always use equals(). The == operator should never be used to compare String content in production code.
Q2. Why does == sometimes return true for two different String variables?
Because of the String pool. When Java encounters a string literal like "hello", it checks the pool first. If "hello" already exists there, it reuses the same object rather than creating a new one. Two variables both assigned the literal "hello" end up pointing to the same pooled object, so == returns true. This only works for compile-time literals. Strings from user input, files, databases, and API calls are always heap objects — == will return false for them even if the content matches.
Q3. What is the safest pattern to compare a String when the variable might be null?
Three safe patterns: first, put the known non-null string on the left — "expected".equals(variable) returns false safely if variable is null. Second, use Objects.equals(a, b) from java.util.Objects — this handles both sides being null and returns true when both are null. Third, explicitly check for null before comparing — if (variable != null && variable.equals("expected")). The constant-on-left pattern is the most commonly adopted in Java production code.
Q4. What does compareTo() return and how should you use the result?
compareTo() returns an int. Zero means the strings are equal. A negative integer means the calling string comes before the argument alphabetically. A positive integer means it comes after. The actual magnitude varies — it does not always return 1 or -1. Always use > 0, < 0, and == 0 to interpret the result, never compare it to a specific number. compareTo() is used for sorting and ordering — whenever you need to determine relative alphabetical position rather than simple equality.
Q5. When should you use equalsIgnoreCase() instead of equals()?
Use equalsIgnoreCase() whenever the caller's capitalisation should not affect the comparison result — city names, role names, status codes, search queries, product categories, and user-supplied identifiers. For security-sensitive comparisons like passwords, use equals() — case matters there. For API responses or database values with a controlled format, equals() is appropriate. The general rule: if you would normalise both strings to the same case before comparing, use equalsIgnoreCase() directly — it is cleaner and avoids creating two temporary lowercase strings.
Q6. What is the difference between Objects.equals() and String.equals()?
String.equals() is an instance method — calling it on a null reference throws NullPointerException. Objects.equals(a, b) is a static utility method from java.util.Objects that handles null on both sides: if both are null, it returns true; if one is null, it returns false; if neither is null, it delegates to a.equals(b). Use Objects.equals() when either value might be null and you do not want to write a null check manually. Use String.equals() when you know the left side is non-null.
FAQs
Is String comparison in Java case-sensitive by default?
Yes. equals(), compareTo(), contains(), startsWith(), endsWith(), and indexOf() are all case-sensitive. Comparing "Admin" to "admin" with equals() returns false. Use equalsIgnoreCase() or compareToIgnoreCase() when you need case-insensitive comparison, or normalise both strings to lowercase first with toLowerCase().
Why is "hello" == "hello" true but new String("hello") == new String("hello") false?
String literals are stored in the String pool and reused. Both "hello" literals resolve to the same pooled object, so their references are equal. new String("hello") creates a new object in heap memory every time, bypassing the pool. Each call produces a separate object with a different address, so == returns false. This is why == for Strings is unreliable — its result depends on how the strings were created, not on their content.
Can you use compareTo() to check equality instead of equals()?
Technically yes — a.compareTo(b) == 0 is true when the strings are equal. But it is not recommended. equals() is more readable, more clearly communicates intent, and handles the comparison at the conceptual level of "same content". compareTo() communicates ordering. Using compareTo() for equality checks confuses readers who will wonder why sorting logic appears where equality is expected.
Does equalsIgnoreCase() work with all languages and Unicode characters?
For ASCII characters (A-Z, a-z), equalsIgnoreCase() works correctly and consistently. For Unicode characters beyond ASCII — accented letters, Cyrillic, etc. — Java's case folding may not match the full Unicode case-mapping rules in all locales. For locale-sensitive comparison, use java.text.Collator which applies locale-specific rules. For most Indian language text stored as Unicode, simple equalsIgnoreCase() works correctly.
What happens when you call compareTo() on a null string?
Calling "hello".compareTo(null) throws NullPointerException. The compareTo() method does not handle null arguments. If either string might be null, check for null before calling compareTo(), or use java.util.Comparator.nullsFirst() or nullsLast() when sorting with null-tolerant ordering.
Summary
String comparison in Java has four tools and four distinct purposes. == for reference identity — rarely the right choice for strings. equals() for exact content match — the default for most comparisons. equalsIgnoreCase() for content match when capitalisation is irrelevant — user input, roles, categories. compareTo() for ordering — sorting, finding the earliest or latest in a sequence.
The two habits that prevent most comparison bugs: always use equals() instead of ==, and always put the known non-null value on the left side of equals(). Add Objects.equals() for cases where both sides might be null, and equalsIgnoreCase() for user-supplied strings where case should not matter.
What to Read Next
| Topic | Link |
|---|---|
| How String basics and immutability explain why == and equals differ | Java String Basics → |
| How the String pool works inside JVM heap and stack memory | Java Heap and Stack Memory → |
| How equals() and hashCode() contract affects String use in HashMap and HashSet | Java equals() and hashCode() → |
| How compareTo() powers Collections.sort and Arrays.sort for String arrays | Java Arrays Class Methods → |
| How StringBuilder avoids creating multiple String objects during building | Java StringBuilder and StringBuffer → |