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 ==.
// File: ReferenceComparisonDemo.java
public class ReferenceComparisonDemo {
public static void main(String[] args) {
// Literals — stored in the String Pool — same object reused
String a = "hello";
String b = "hello";
String c = "world";
System.out.println("=== String Pool (literals) ===");
System.out.println("a == b : " + (a == b)); // true — same pool object
System.out.println("a == c : " + (a == c)); // false — different pool object
System.out.println();
// new String() — always creates a new object outside the pool
String x = new String("hello");
String y = new String("hello");
System.out.println("=== Heap Objects (new keyword) ===");
System.out.println("a == x : " + (a == x)); // false — pool vs heap
System.out.println("x == y : " + (x == y)); // false — two separate heap objects
System.out.println("x == x : " + (x == x)); // true — same reference to itself
System.out.println();
// What == actually checks — memory address
System.out.println("=== Identity Hash Codes ===");
System.out.println("a hashCode: " + System.identityHashCode(a));
System.out.println("b hashCode: " + System.identityHashCode(b)); // same as a
System.out.println("x hashCode: " + System.identityHashCode(x)); // different
System.out.println("y hashCode: " + System.identityHashCode(y)); // different from x too
}
}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.
// File: EqualsDemo.java
public class EqualsDemo {
public static void main(String[] args) {
// equals() checks content — not memory location
String s1 = new String("Java");
String s2 = new String("Java");
String s3 = new String("java"); // lowercase j
System.out.println("=== equals() ===");
System.out.println("s1.equals(s2) : " + s1.equals(s2)); // true — same content
System.out.println("s1.equals(s3) : " + s1.equals(s3)); // false — 'J' != 'j'
System.out.println("s1 == s2 : " + (s1 == s2)); // false — different objects
System.out.println();
// equals() with null — calling on a null reference throws NPE
String username = null;
// RISKY — throws NullPointerException if username is null
// if (username.equals("admin")) { ... }
// SAFE — put the known non-null constant on the LEFT
System.out.println("=== Null Safety ===");
System.out.println("\"admin\".equals(null) : " + "admin".equals(username)); // false — no NPE
System.out.println("\"admin\".equals(\"\") : " + "admin".equals("")); // false
System.out.println("\"admin\".equals(\"admin\"): " + "admin".equals("admin")); // true
System.out.println();
// Practical use — credential validation
String storedPassword = "Secret@123";
String[] attempts = {"secret@123", "Secret@123", "SECRET@123", "", null};
System.out.println("=== Password Check ===");
for (String attempt : attempts) {
boolean match = storedPassword.equals(attempt);
System.out.printf(" %-15s → %s%n",
"'" + attempt + "'", match ? "GRANTED" : "DENIED");
}
}
}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.
// File: EqualsIgnoreCaseDemo.java
public class EqualsIgnoreCaseDemo {
public static void main(String[] args) {
String stored = "bengaluru";
String input1 = "Bengaluru";
String input2 = "BENGALURU";
String input3 = "BenGaLuRu";
String input4 = "Mumbai";
System.out.println("=== equalsIgnoreCase() ===");
System.out.println("stored: " + stored);
System.out.printf(" %-12s → %s%n", input1, stored.equalsIgnoreCase(input1)); // true
System.out.printf(" %-12s → %s%n", input2, stored.equalsIgnoreCase(input2)); // true
System.out.printf(" %-12s → %s%n", input3, stored.equalsIgnoreCase(input3)); // true
System.out.printf(" %-12s → %s%n", input4, stored.equalsIgnoreCase(input4)); // false
System.out.println();
// Practical use 1 — search feature (case-insensitive product name match)
String[] productNames = {
"Wireless Headphones", "USB Hub", "Laptop Stand",
"Mechanical Keyboard", "LED Monitor"
};
String searchQuery = "laptop stand";
System.out.println("=== Product Search: '" + searchQuery + "' ===");
for (String product : productNames) {
if (product.equalsIgnoreCase(searchQuery)) {
System.out.println(" Found: " + product);
}
}
System.out.println();
// Practical use 2 — role-based access control
String userRole = "MANAGER";
System.out.println("=== Role Check: " + userRole + " ===");
if ("admin".equalsIgnoreCase(userRole)) {
System.out.println(" Full access granted.");
} else if ("manager".equalsIgnoreCase(userRole)) {
System.out.println(" Reports and team access granted.");
} else if ("viewer".equalsIgnoreCase(userRole)) {
System.out.println(" Read-only access granted.");
} else {
System.out.println(" Unknown role — access denied.");
}
System.out.println();
// Difference between equals and equalsIgnoreCase
String p = "PRIYA";
String q = "priya";
System.out.println("equals() : " + p.equals(q)); // false
System.out.println("equalsIgnoreCase(): " + p.equalsIgnoreCase(q)); // true
}
}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.
// File: CompareToDemo.java
import java.util.Arrays;
public class CompareToDemo {
public static void main(String[] args) {
// compareTo() return values
String apple = "Apple";
String banana = "Banana";
String cherry = "Cherry";
String apple2 = "Apple";
System.out.println("=== compareTo() ===");
System.out.println("'Apple'.compareTo('Banana') : " + apple.compareTo(banana)); // negative — A before B
System.out.println("'Banana'.compareTo('Apple') : " + banana.compareTo(apple)); // positive — B after A
System.out.println("'Apple'.compareTo('Apple') : " + apple.compareTo(apple2)); // 0 — equal
System.out.println("'Apple'.compareTo('Cherry') : " + apple.compareTo(cherry)); // negative
System.out.println();
// compareTo() with case sensitivity
String upper = "APPLE";
String lower = "apple";
System.out.println("'APPLE'.compareTo('apple') : " + upper.compareTo(lower)); // negative — uppercase A < lowercase a in Unicode
System.out.println("compareToIgnoreCase : " + upper.compareToIgnoreCase(lower)); // 0
System.out.println();
// Practical use — sort an array of city names
String[] cities = {"Pune", "Delhi", "Bengaluru", "Mumbai", "Chennai", "Hyderabad"};
System.out.println("Before sort: " + Arrays.toString(cities));
Arrays.sort(cities); // Arrays.sort uses compareTo() internally
System.out.println("After sort : " + Arrays.toString(cities));
System.out.println();
// Custom sort — longer names first, then alphabetical
String[] names = {"Rohan", "Ananya", "Priya", "Deepak", "Sneha", "Karan"};
Arrays.sort(names, (a, b) -> {
if (a.length() != b.length()) return b.length() - a.length(); // longer first
return a.compareTo(b); // then alphabetical
});
System.out.println("Sorted (longer first): " + Arrays.toString(names));
System.out.println();
// compareToIgnoreCase — case-insensitive sort
String[] mixed = {"banana", "Apple", "cherry", "DATE", "elderberry"};
Arrays.sort(mixed, String::compareToIgnoreCase);
System.out.println("Case-insensitive sort: " + Arrays.toString(mixed));
}
}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.
// File: StringPoolDemo.java
public class StringPoolDemo {
public static void main(String[] args) {
// Compile-time constants — JVM interns them to the pool
String a = "hello";
String b = "hel" + "lo"; // compile-time concatenation — same pool object
System.out.println("=== Compile-time Constants ===");
System.out.println("a == b : " + (a == b)); // true — both in pool
String prefix = "hel";
String c = prefix + "lo"; // runtime concatenation — new heap object
System.out.println("a == c : " + (a == c)); // false — c is on heap
System.out.println();
// intern() — force a string into the pool
String d = new String("hello");
String e = d.intern(); // returns the pooled version
System.out.println("=== intern() ===");
System.out.println("a == d : " + (a == d)); // false — d is on heap
System.out.println("a == e : " + (a == e)); // true — e is the pool object
System.out.println("a.equals(d) : " + a.equals(d)); // true — same content
System.out.println();
// Why you should NEVER rely on == for string comparison
String userInput = getUserInput(); // returns new String("admin") from user
String adminRole = "admin";
boolean wrongCheck = (userInput == adminRole); // false — different objects
boolean rightCheck = adminRole.equals(userInput); // true — same content
System.out.println("=== Why == Fails in Practice ===");
System.out.println("Wrong (==) : " + wrongCheck); // false — bug!
System.out.println("Correct (equals): " + rightCheck); // true
}
// Simulates getting a string from user input, database, or API
// These always create new String objects — never from the pool
static String getUserInput() {
return new String("admin");
}
}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.
// File: NullSafeComparisonDemo.java
import java.util.Objects;
public class NullSafeComparisonDemo {
public static void main(String[] args) {
String known = "admin";
String maybeNull = null;
String alsoNull = null;
// Pattern 1 — constant on the left (most common)
System.out.println("=== Pattern 1: Constant on Left ===");
System.out.println(known.equals(maybeNull)); // false — no NPE
System.out.println("admin".equals(maybeNull)); // false — no NPE
System.out.println("admin".equalsIgnoreCase(null)); // false — no NPE
System.out.println();
// Pattern 2 — explicit null check
System.out.println("=== Pattern 2: Explicit Null Check ===");
String input = null;
if (input != null && input.equals("admin")) {
System.out.println("Match");
} else {
System.out.println("No match (null or different)");
}
System.out.println();
// Pattern 3 — Objects.equals() — null-safe on BOTH sides
System.out.println("=== Pattern 3: Objects.equals() ===");
System.out.println(Objects.equals(maybeNull, alsoNull)); // true — both null
System.out.println(Objects.equals(known, maybeNull)); // false — one is null
System.out.println(Objects.equals(known, "admin")); // true — same content
System.out.println(Objects.equals(null, null)); // true
System.out.println();
// Pattern 4 — Optional (Java 8+)
System.out.println("=== Pattern 4: String.valueOf() ===");
String value = null;
String safe = String.valueOf(value); // "null" string if null
System.out.println("safe equals 'null': " + "null".equals(safe)); // true — converts null to "null" string
System.out.println("safe value: '" + safe + "'");
System.out.println();
// Comparison of null vs empty vs blank
String[] cases = {null, "", " ", "hello"};
System.out.println("=== Null / Empty / Blank Classification ===");
for (String s : cases) {
String label = s == null ? "NULL"
: s.isEmpty() ? "EMPTY"
: s.isBlank() ? "BLANK (whitespace)"
: "HAS CONTENT";
System.out.printf(" %-10s → %s%n", "'" + s + "'", label);
}
}
}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).
Basic Example
public class Main {
public static void main(String[] args) {
String a = "apple";
String b = "Apple";
String c = new String("apple");
System.out.println("a == c : " + (a == c));
System.out.println("a.equals(b) : " + a.equals(b));
System.out.println("a.equalsIgnoreCase : " + a.equalsIgnoreCase(b));
System.out.println("a.compareTo(b) : " + a.compareTo(b));
}
}Output
a == c : false a.equals(b) : false a.equalsIgnoreCase : true a.compareTo(b) : 32
How it works:
- ›
a == cisfalsebecausecwas created withnew String(...), making it a separate object from the pooled literala, even though the text matches. - ›
a.equals(b)isfalsebecauseequals()is case-sensitive, and"apple"does not exactly match"Apple". - ›
a.equalsIgnoreCase(b)istruebecause it treats'a'and'A'as the same letter. - ›
a.compareTo(b)returns32— a positive number — because lowercase'a'(Unicode 97) comes after uppercase'A'(Unicode 65) in the character set.
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
String city1 = new String("Mumbai");
String city2 = new String("Mumbai");
if (city1 == city2) {
System.out.println("Same city"); // NEVER prints — different heap objects
}
// Correct
if (city1.equals(city2)) {
System.out.println("Same city"); // prints correctly
}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
String userRole = getUserRoleFromDatabase(); // might return null
// Throws NullPointerException if userRole is null
if (userRole.equals("admin")) { // NPE here when null
grantAccess();
}
// Fix — constant on left
if ("admin".equals(userRole)) { // returns false if null — no NPE
grantAccess();
}
// Or — Objects.equals for null-safe symmetric comparison
if (Objects.equals(userRole, "admin")) {
grantAccess();
}Mistake 3 — Using equals() Instead of equalsIgnoreCase() for User Input
String statusFromUser = "Active"; // user might type "ACTIVE", "active", "Active"
String storedStatus = "active";
// equals() fails for differently-cased inputs
if (storedStatus.equals(statusFromUser)) { // false — 'A' != 'a'
System.out.println("User is active"); // never prints
}
// Fix
if (storedStatus.equalsIgnoreCase(statusFromUser)) { // true — case ignored
System.out.println("User is active");
}Mistake 4 — Misreading compareTo() Return Value
String a = "Mango";
String b = "Apple";
int result = a.compareTo(b); // positive — M comes after A
// Wrong interpretation
if (result == 1) {
System.out.println("Mango comes after Apple"); // might not always be exactly 1
}
// Correct interpretation — check sign, not specific value
if (result > 0) {
System.out.println("Mango comes after Apple"); // correct
} else if (result < 0) {
System.out.println("Mango comes before Apple");
} else {
System.out.println("Equal");
}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
See why == and equals() don't always give the same answer.
Practice Programs
Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.
Compares two strings and reports their lexicographic order.
Check whether two strings are anagrams of each other.
Determines whether one string is a rotation of another.
Sorts the words of a sentence into alphabetical order.
Finds the longest starting substring shared by every word in an array.