Contents
- equals() vs ==
- compareTo() and compareToIgnoreCase()
- contains, startsWith, endsWith, matches
- Locale-aware Comparison with Collator
- Null-safe Comparison
The == operator compares object references — memory addresses — not string content. String literals are interned into a pool, so two literals with the same content often happen to be the same object and == returns true by coincidence. But strings obtained from user input, file reads, JSON parsing, or new String(...) are heap objects, and == returns false even when the content is identical. Always use equals() for content comparison. Use equalsIgnoreCase() for case-insensitive comparison, but note it is not locale-aware — use Collator when locale correctness matters.
String a = "hello";
String b = "hello";
String c = new String("hello"); // explicitly creates a new object
// == compares references (memory addresses)
System.out.println(a == b); // true — string pool: same object
System.out.println(a == c); // false — different object in heap
System.out.println(a == "hello");// true — string pool literal
// equals() compares content
System.out.println(a.equals(b)); // true
System.out.println(a.equals(c)); // true — content is same
System.out.println(a.equals(null)); // false (no NPE)
// equalsIgnoreCase() — case-insensitive content comparison
String s1 = "Java";
String s2 = "JAVA";
System.out.println(s1.equalsIgnoreCase(s2)); // true
// NB: equalsIgnoreCase is NOT locale-aware — use Collator for correct locale behavior
// contentEquals — compare with CharSequence (StringBuilder etc.)
StringBuilder sb = new StringBuilder("hello");
System.out.println(a.equals(sb)); // false — different types
System.out.println(a.contentEquals(sb)); // true — content comparison
// Safe pattern — put the known non-null string on the left to avoid NPE
String input = getUserInput(); // may be null
if ("ACTIVE".equals(input)) { /* ... */ } // safe
// if (input.equals("ACTIVE")) { } // NPE if input is null
// Objects.equals — null-safe, does deep content comparison
System.out.println(Objects.equals(null, null)); // true
System.out.println(Objects.equals("hi", null)); // false
System.out.println(Objects.equals("hi", "hi")); // true
Never use == to compare String content. Even though string literals are interned into the pool, strings coming from user input, file reads, JSON parsing, or new String(...) are separate objects — == will return false for equal content.
compareTo() returns 0 if the strings are equal, a negative value if the receiver is lexicographically less than the argument, and a positive value if it is greater. The comparison is based on Unicode code point order, which is the foundation for sorting. compareToIgnoreCase() performs the same comparison while ignoring case differences, but it is not locale-aware — it simply folds characters to a canonical case using Unicode rules. For locale-correct sorting of human-readable text (especially accented characters), use Collator instead.
// compareTo() — lexicographic comparison based on Unicode code points
// Returns: negative if this < other, 0 if equal, positive if this > other
String x = "apple";
String y = "banana";
int result = x.compareTo(y); // negative — 'a' (97) < 'b' (98)
System.out.println(result); // e.g. -1
System.out.println("abc".compareTo("abc")); // 0 — equal
System.out.println("abc".compareTo("abd")); // -1 — 'c' < 'd'
System.out.println("abd".compareTo("abc")); // 1 — 'd' > 'c'
System.out.println("abc".compareTo("abcd")); // -1 — shorter is "less"
// Case sensitivity: uppercase letters come before lowercase in Unicode
System.out.println("Z".compareTo("a")); // negative! 'Z'=90, 'a'=97
// compareToIgnoreCase() — case-insensitive lexicographic order
System.out.println("Z".compareToIgnoreCase("a")); // positive — 'z' > 'a'
System.out.println("Apple".compareToIgnoreCase("apple")); // 0
// Using compareTo() in sorting
List<String> words = new ArrayList<>(List.of("banana", "Apple", "cherry"));
Collections.sort(words); // ["Apple", "banana", "cherry"]
Collections.sort(words, String::compareToIgnoreCase); // ["Apple", "banana", "cherry"]
// For locale-correct sort, use Collator instead (see below)
// Comparator using String comparison
Comparator<String> byLength = Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder());
words.sort(byLength);
These methods return a boolean and are case-sensitive. contains() searches anywhere in the string for the given subsequence. startsWith() and endsWith() check the beginning and end; startsWith(prefix, offset) can check from a specific character position. matches() tests the string against a full regular expression — the entire string must match (it is implicitly anchored), unlike Pattern.matcher(s).find() which looks for the pattern anywhere. Use matches() for format validation; use contains() or indexOf() for substring searches.
String s = "The quick brown fox";
// contains — substring check
System.out.println(s.contains("quick")); // true
System.out.println(s.contains("slow")); // false
// contains(CharSequence) — works with String, StringBuilder, etc.
// startsWith / endsWith
System.out.println(s.startsWith("The")); // true
System.out.println(s.startsWith("quick", 4));// true — starting from index 4
System.out.println(s.endsWith("fox")); // true
// indexOf — position of substring (-1 if not found)
System.out.println(s.indexOf("quick")); // 4
System.out.println(s.indexOf("o")); // 11 (first occurrence)
System.out.println(s.lastIndexOf("o")); // 17 (last occurrence)
// matches — check against a regex pattern
System.out.println("hello123".matches("[a-z]+\\d+")); // true
System.out.println("hello".matches("[a-z]+")); // true
// matches() must match the ENTIRE string (anchored implicitly)
System.out.println("hello world".matches("[a-z]+")); // false (space not matched)
// Use contains() or Pattern.matcher().find() for partial matches
// regionMatches — compare subregions of two strings
String s1 = "hello world";
String s2 = "WORLD";
System.out.println(s1.regionMatches(6, "world", 0, 5)); // true (case-sensitive)
System.out.println(s1.regionMatches(true, 6, s2, 0, 5)); // true (ignoreCase=true)
String.compareTo() orders by Unicode code points, which does not respect how people in a given language expect words to sort. For example, in German, "ä" should sort near "a", but its Unicode code point places it far after "z". Collator from java.text implements locale-aware linguistic ordering — it handles accented characters, ligatures, case sensitivity levels, and language-specific rules. Obtain one via Collator.getInstance(Locale) and use it as a Comparator to sort collections. For large datasets, pre-compute CollationKey objects to avoid re-sorting overhead.
import java.text.Collator;
import java.util.Locale;
// String.compareTo() uses Unicode code points — not suitable for human-readable sorting
// "ä" comes after "z" in Unicode but should sort near "a" in German
List<String> names = new ArrayList<>(List.of("Müller", "Maier", "Ärger", "Bauer"));
// Raw sort — wrong for German names
Collections.sort(names);
System.out.println(names); // [Bauer, Maier, Müller, Ärger] — "Ä" sorts last, wrong
// Collator — locale-aware, correct linguistic sort
Collator collator = Collator.getInstance(Locale.GERMAN);
names.sort(collator);
System.out.println(names); // [Ärger, Bauer, Maier, Müller] — correct
// Collator strength levels
Collator primary = Collator.getInstance(Locale.ENGLISH);
primary.setStrength(Collator.PRIMARY); // "a" == "A" == "ä"
primary.setStrength(Collator.SECONDARY); // "a" == "A", but "a" != "ä"
primary.setStrength(Collator.TERTIARY); // default: "a" != "A", "a" != "ä"
// Collator.compare() — same contract as compareTo()
int cmp = collator.compare("Müller", "Maier"); // negative in German sort
// Pre-compute sort keys for large datasets (expensive comparison is done once)
Collator.CollationKey key1 = collator.getCollationKey("Müller");
Collator.CollationKey key2 = collator.getCollationKey("Maier");
int result = key1.compareTo(key2);
For user-visible sorted lists (search results, name dropdowns, address books), always use Collator with the appropriate locale. Plain compareTo() gives incorrect results for accented characters, ligatures, and locale-specific ordering rules.
Calling equals() or compareTo() on a null reference throws NullPointerException. The Yoda condition — putting the known non-null literal on the left, e.g., "ACTIVE".equals(input) — avoids the NPE when the variable might be null. Objects.equals(a, b) handles both arguments being null and is the cleanest null-safe equality check. String.valueOf(obj) converts a null reference to the string "null" rather than throwing, which is useful when building display strings. For sorting collections that may contain null elements, use Comparator.nullsFirst() or Comparator.nullsLast().
// Calling methods on a null String throws NullPointerException
String s = null;
// s.equals("hello"); // NPE!
// s.compareTo("hello"); // NPE!
// Safe patterns:
// 1. Put the literal or known-non-null string on the left
"hello".equals(s); // false, no NPE
"hello".equalsIgnoreCase(s); // false, no NPE
// 2. Objects.equals — both arguments may be null
Objects.equals(s, "hello"); // false
Objects.equals(null, null); // true
// 3. String.valueOf — converts null to the string "null"
String safe = String.valueOf(s); // "null"
// Null-safe comparator for sorting (nulls first or last)
List<String> withNulls = new ArrayList<>(Arrays.asList("b", null, "a", null, "c"));
withNulls.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println(withNulls); // [null, null, a, b, c]
withNulls.sort(Comparator.nullsLast(Comparator.naturalOrder()));
System.out.println(withNulls); // [a, b, c, null, null]