Java Tutorial
🔍

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

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

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

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

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

Java
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 checksMemory address (reference)Character content, case-sensitiveCharacter content, case-insensitiveAlphabetical order
Return typebooleanbooleanbooleanint (0, positive, negative)
Case sensitiveN/AYesNoYes
Null safeYes — null == null is trueNo — NPE if called on nullNo — NPE if called on nullNo — NPE if called on null
Works on primitivesYesNo — method, needs objectsNoNo
Used for sortingNoNoNoYes — used in Arrays.sort
When to useNever for content; identity checks onlyContent equality (case matters)Content equality (case irrelevant)Sorting, ordering, finding min/max
Fails silentlyYes — returns false instead of crashingNo — but NPE risk on nullNo — but NPE risk on nullNo — but NPE risk on null
Pool-awareYes — literals may share addressNo — checks characters, not addressNoNo
Safe patternAvoid 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.

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

Java
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}
Java
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}
Java
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

Java
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

Java
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

Java
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

Java
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

TopicLink
How String basics and immutability explain why == and equals differJava String Basics →
How the String pool works inside JVM heap and stack memoryJava Heap and Stack Memory →
How equals() and hashCode() contract affects String use in HashMap and HashSetJava equals() and hashCode() →
How compareTo() powers Collections.sort and Arrays.sort for String arraysJava Arrays Class Methods →
How StringBuilder avoids creating multiple String objects during buildingJava StringBuilder and StringBuffer →
Java String Comparison | DevStackFlow