String Compare

Su Suriya Ravichandran Updated 14 Nov 2026
5 min read ·Lesson 95 of 133

Comparing strings answers two different questions: are these two strings the same text? (equality) and which one comes first? (ordering). Java provides separate methods for each, with case-sensitive and case-insensitive variants, plus Collator for language-aware ordering. Using the right one, and never using == for content, prevents some of the most common bugs in Java programs.

Equality: equals, Not ==

String s1 = "hello";
String s3 = new String("hello");

System.out.println(s1 == s3);          // false
System.out.println(s1.equals(s3));     // true
  • == compares references: whether both variables point to the same object.
  • equals compares content: whether both strings contain the same characters in the same order.

Two strings with identical text are often different objects, especially when one was read from a file, typed by a user, received over the network, or built at runtime. == then returns false even though the text matches. Because string literals are pooled, == sometimes appears to work in small tests, which makes the bug easy to miss and hard to find later.

Rule: always use equals (or a variant) to compare string content.

Null-safe equality

Calling equals on a null reference throws NullPointerException. Two safe patterns:

String input = null;

"yes".equals(input);                 // false: the literal is never null
Objects.equals(input, "yes");        // false: handles null on either side
Objects.equals(null, null);          // true

Putting the known non-null value (often a literal or constant) on the left is a common Java idiom.

Ignoring case

"Hello".equalsIgnoreCase("HELLO");   // true

equalsIgnoreCase compares character by character, treating upper- and lowercase forms as equal. It is the right tool for comparing user input against fixed keywords ("yes", "quit"), file extensions, and HTTP header names.

Comparing with other CharSequences

equals returns false for anything that is not a String, even a StringBuilder with the same text. Use contentEquals:

"hello".equals(new StringBuilder("hello"));          // false: different types
"hello".contentEquals(new StringBuilder("hello"));   // true

Ordering: compareTo

compareTo determines which string comes first, character by character, using each character's UTF-16 code:

int r = a.compareTo(b);
// r < 0  → a comes before b
// r == 0 → same text
// r > 0  → a comes after b
"apple".compareTo("apricot");     // -2: 'p' (112) vs 'r' (114) at index 2
"app".compareTo("apple");         // -2: "app" is a prefix, so shorter comes first (3 - 5)
"B".compareToIgnoreCase("a");     // 1: 'b' after 'a' when case is ignored

The algorithm finds the first position where the strings differ and returns the difference between those two characters. If one string is a prefix of the other, it returns the difference in lengths. Only the sign of the result is meaningful; do not test for specific values like -1.

compareTo returns 0 exactly when equals returns true, so the two are consistent.

Code-point order is not alphabetical order

Because compareTo compares character codes, it puts all uppercase letters (A–Z, 65–90) before all lowercase letters (a–z, 97–122), and places accented letters (such as É, 201) after both:

List<String> names = new ArrayList<>(List.of("émile", "Zara", "adam", "Émile", "zoe", "Ada", "ada"));
Collections.sort(names);
// [Ada, Zara, ada, adam, zoe, Émile, émile]

"Zara" before "ada" is correct by code point, but no human would sort a contact list that way.

Case-Insensitive Ordering

String.CASE_INSENSITIVE_ORDER is a ready-made comparator, and compareToIgnoreCase is the method equivalent:

names.sort(String.CASE_INSENSITIVE_ORDER);
// [Ada, ada, adam, Zara, zoe, émile, Émile]

Case no longer splits the list, but accented letters still sort after z.

Language-Aware Ordering: Collator

java.text.Collator compares strings according to the rules of a natural language, the way dictionaries and phone books do:

Collator collator = Collator.getInstance(Locale.ENGLISH);
names.sort(collator);
// [ada, Ada, adam, émile, Émile, Zara, zoe]

Now émile sorts with the es, and case is treated as a minor difference rather than a major one.

A collator's strength controls which differences matter:

StrengthDistinguishes"a" vs "A""e" vs "é"
PRIMARYBase letters onlyequalequal
SECONDARYBase letters and accentsequaldifferent
TERTIARY (default)Base letters, accents, and casedifferentdifferent

PRIMARY strength is useful for "these are effectively the same name" checks, such as finding duplicates that differ only in case or accents.

Use Collator for anything shown to users in sorted order: names, cities, product titles. Use plain compareTo for machine-oriented ordering (IDs, codes, keys), where consistency and speed matter more than linguistic conventions.

Comparing Parts of Strings

MethodChecks
startsWith(prefix) / endsWith(suffix)Beginning or end
contains(s)Anywhere
regionMatches(ignoreCase, offset, other, otherOffset, len)A region of one string against a region of another
matches(regex)Whole string against a pattern
"Java Tutorial".regionMatches(true, 5, "TUTOR", 0, 5);   // true: case-insensitive region match

regionMatches compares part of a string without creating a substring.

Normalizing Before Comparing

User-entered text often differs in ways that should not matter. Normalize both sides first:

static boolean sameEmail(String a, String b) {
    return a.strip().equalsIgnoreCase(b.strip());
}

For text with accents, Unicode normalization (java.text.Normalizer) makes visually identical strings compare equal even if they were typed with different combinations of characters, such as a precomposed é versus e followed by a combining accent.

Summary of Methods

GoalMethod
Same text, exacta.equals(b)
Same text, null-safeObjects.equals(a, b) or "literal".equals(x)
Same text, ignoring casea.equalsIgnoreCase(b)
Same text as a StringBuildera.contentEquals(sb)
Order, by code pointa.compareTo(b)
Order, ignoring casea.compareToIgnoreCase(b) / String.CASE_INSENSITIVE_ORDER
Order, as a human would expectCollator.getInstance(locale)
Same object (rarely needed)a == b

Practical Example: Sorting a Contact List

import java.text.Collator;
import java.util.*;

public class Contacts {
    public static void main(String[] args) {
        List<String> names = List.of("émile", "Zara", "adam", "Émile", "zoe", "Ada", "ada");

        List<String> natural = new ArrayList<>(names);
        Collections.sort(natural);
        System.out.println("compareTo:        " + natural);

        List<String> ignoreCase = new ArrayList<>(names);
        ignoreCase.sort(String.CASE_INSENSITIVE_ORDER);
        System.out.println("ignore case:      " + ignoreCase);

        Collator collator = Collator.getInstance(Locale.ENGLISH);
        List<String> human = new ArrayList<>(names);
        human.sort(collator);
        System.out.println("Collator:         " + human);

        collator.setStrength(Collator.PRIMARY);
        Set<String> unique = new TreeSet<>(collator);
        unique.addAll(names);
        System.out.println("unique (primary): " + unique);
    }
}

Output:

compareTo:        [Ada, Zara, ada, adam, zoe, Émile, émile]
ignore case:      [Ada, ada, adam, Zara, zoe, émile, Émile]
Collator:         [ada, Ada, adam, émile, Émile, Zara, zoe]
unique (primary): [Ada, adam, émile, Zara, zoe]

The same seven names, sorted three ways, show why the choice of comparison matters:

  • compareTo follows character codes. Uppercase names come first, then lowercase, and accented names end up last.
  • Case-insensitive ordering fixes the uppercase/lowercase split but still puts émile after zoe.
  • The Collator produces the order a person would expect in an address book.

The last line uses a PRIMARY-strength collator as the TreeSet's comparator. At that strength, Ada/ada and émile/Émile compare as equal, so the set keeps only the first of each and acts as a duplicate detector that ignores both case and accents.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about String Compare and get answers drawn from it.

Signed-in readers only.