W3docs

Java String Comparison

Compare Java strings correctly with equals, equalsIgnoreCase, compareTo, and understand why == compares references.

Comparing two strings looks like the most innocent operation in the language, but it's where new Java programmers stub their toe most often. The reason is that == is the obvious-looking syntax for "are these the same?" — and for strings, it answers a question you almost never want answered. This chapter walks through the comparison API in the order you should reach for it, and ends with the rules for ordering — case-folding, locale, numeric strings, and the trap of relying on the JVM's default sort.

Why == is wrong for strings

== compares references for objects. It asks "are these two variables pointing at literally the same object on the heap?". For strings that share identity through the string pool, == happens to return true. For anything else — strings built at runtime, strings parsed from input, strings returned from new String(...) — it returns false even when the contents are identical.

String a = "hello";
String b = "hello";
String c = new String("hello");
String d = "hel" + new String("lo");

a == b;             // true  — both pooled literals
a == c;             // false — c is a fresh object
a == d;             // false — d is built at runtime
a.equals(c);        // true  — contents match
a.equals(d);        // true  — contents match

The rule is short and absolute: for string equality, use equals. Always. No matter where the strings came from. == "happens to work" in early tests with literals, then silently goes wrong the first time a string travels through I/O.

equals and equalsIgnoreCase

equals returns true iff the two strings have the same characters in the same order:

"hello".equals("hello");           // true
"hello".equals("Hello");           // false — case-sensitive
"hello".equals(null);              // false — never throws

equalsIgnoreCase does a character-by-character comparison after Unicode case-folding:

"hello".equalsIgnoreCase("HELLO");      // true
"hello".equalsIgnoreCase("Hello");      // true
"straße".equalsIgnoreCase("STRASSE");   // false — ß and SS differ in length

The German ß example is a useful warning: case-folding in Unicode isn't a bijection (ß lowercases to itself, but it also matches ss in some contracts). If user input might contain non-ASCII text and you need case-insensitive matching, prefer normalising both sides with String#toLowerCase(Locale) before comparing, or use java.text.Collator for locale-aware matching.

null-safe comparison

equals on a null receiver throws NullPointerException. So this is a bug:

if (input.equals("admin")) { ... }     // NPE when input is null

Three idiomatic fixes:

"admin".equals(input)                  // literal on the left — handles null safely
Objects.equals(input, "admin")         // null-safe symmetric comparison (java.util.Objects)
Objects.requireNonNull(input).equals("admin")  // fail-fast if null is a bug

"literal".equals(variable) — sometimes called Yoda comparison — is the most common in established codebases. Objects.equals is slightly nicer when neither side is statically known.

compareTo and compareToIgnoreCase

When you want order (sort, range check, binary search), compareTo returns an int:

  • < 0 if the receiver sorts before the argument
  • 0 if they are equal
  • > 0 if the receiver sorts after the argument
"apple".compareTo("banana");        // negative
"banana".compareTo("apple");        // positive
"apple".compareTo("apple");         // 0

The comparison is by Unicode code unit (UTF-16), one character at a time, with shorter strings sorting before longer ones when one is a prefix of the other. That gives you a defined total order — but not the order a human would call "alphabetical":

"Z".compareTo("a");                 // negative — 'Z' is 0x5A, 'a' is 0x61
"apple".compareTo("Banana");        // positive — uppercase letters come first in ASCII

For human-facing sort, two real answers:

  • compareToIgnoreCase for the cheap, ASCII-leaning fix that handles English well.
  • java.text.Collator for properly locale-aware sort that handles accents, German ß, the right place for ñ in Spanish, and the convention that some scripts put numerals after letters.
Collator c = Collator.getInstance(Locale.FRENCH);
List<String> names = new ArrayList<>(List.of("éclair", "Étoile", "anvil"));
names.sort(c);                     // ["anvil", "éclair", "Étoile"]  — proper French sort

If a user is going to see the list, use Collator. If it's a sort key in an internal index, compareTo is faster and stable.

contentEquals and CharSequence

String#contentEquals compares against any CharSequenceString, StringBuilder, StringBuffer, or a CharBuffer. It's the right method when you want to know if a builder's current contents equal a known string without an intermediate toString():

StringBuilder sb = new StringBuilder("hello");
"hello".contentEquals(sb);         // true — no toString allocation

equals won't help here, because String#equals returns false for any non-String argument by contract.

equals ignores the pool entirely

The pool is an internal storage optimisation; equals doesn't consult it. Two strings with the same characters compare equal whether or not they live at the same address:

String pooled = "x";
String fresh  = new String("x");
pooled == fresh;             // false — different objects
pooled.equals(fresh);        // true  — same contents
fresh.hashCode() == pooled.hashCode();  // true — equal strings always hash the same

That last line is the reason String is safe to use as a HashMap key: equal strings always have equal hash codes, and the cache makes lookups cheap.

A worked example

The program walks through the comparison decisions you'll make in real code: pick the right method for equality, handle null, sort with the right comparator. The output makes the differences between compareTo and a Collator visible side-by-side.

java— editable, runs on the server

The three sorts make the differences visible. compareTo gives [Banana, anvil, apple, Étoile, éclair]: the uppercase Banana jumps to the front because B (0x42) sorts before any lowercase letter, and Étoile lands near the end because É (0xC9) is above z. CASE_INSENSITIVE_ORDER produces [anvil, apple, Banana, éclair, Étoile], folding case so the list reads alphabetically. On this particular list the French Collator happens to produce the same order — but it is the only one whose result is correct by construction: it folds case and treats accents as a secondary distinction, so it stays right on inputs where the code-point trick breaks (for example, sorting côté relative to cote, or placing ñ correctly in Spanish).

What's next

Splitting a string into pieces is the natural next topic. Java has two tools for it, and one of them is older than the language design wants you to remember. Continue to Java StringTokenizer.

Practice

Practice
Which call to test whether a (possibly `null`) `input` equals the string `'admin'` is **both** correct and `null`-safe?
Which call to test whether a (possibly `null`) `input` equals the string `'admin'` is **both** correct and `null`-safe?
Was this page helpful?