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

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

// 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:

  1. ›a == c is false because c was created with new String(...), making it a separate object from the pooled literal a, even though the text matches.
  2. ›a.equals(b) is false because equals() is case-sensitive, and "apple" does not exactly match "Apple".
  3. ›a.equalsIgnoreCase(b) is true because it treats 'a' and 'A' as the same letter.
  4. ›a.compareTo(b) returns 32 — 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

Practice Programs

Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.

Browse all Java programs →