Table of Contents

String comparison in Java

  • Last Updated: January 14, 2025
  • By: javahandson
  • Series
img

String comparison in Java

String comparison in Java looks easy until it bites you. This guide walks through equals, equalsIgnoreCase, ==, compareTo, and compareToIgnoreCase with plain examples, so you always pick the right one.

1. Introduction

String comparison in Java trips up more beginners than almost any other topic. You write an if statement, it works on your machine, and then it quietly fails in production. The code looks fine. The logic looks fine. Yet the branch never runs.

Nine times out of ten, the culprit is a single character: the second = in ==.

Here is the thing. A String in Java is an object, not a primitive. So the rules you learned for comparing two int values do not carry over. With numbers, == asks “same value?”. With objects, it asks a completely different question: “same box?”

Java gives you five main tools for this job. Two check content. One checks identity. Two give you an ordering for sorting. Each has a job, and picking the wrong one leads to bugs that hide well.

Let us walk through all five, one at a time, with runnable examples. By the end you will know exactly which method to call and why.

1.1 What This Article Covers

We start with why strings behave differently from numbers. Then we take each comparison method in turn. Here is the plan:

  • Why strings need their own comparison rules
  • The string constant pool, and how it explains surprising results
  • equals and equalsIgnoreCase for content checks
  • The == operator, and why it lies to you sometimes
  • compareTo and compareToIgnoreCase for sorting
  • Handy extras like Objects.equals, contentEquals, and regionMatches
  • Accents, locales, and the traps they hide
  • Common mistakes, a full walkthrough, and interview questions

You only need to know what a String is and how to write a main method. Everything else we build up slowly.

2. Why String Comparison Needs Care

Before we touch a single method, we need to understand what a String variable actually holds. That one idea explains almost every surprise in this article.

2.1 Strings Are Objects, Not Primitives

Think of a primitive like a sticky note. When you write int a = 5, the value 5 sits right there in the variable. Comparing two sticky notes means reading both numbers. Simple.

A String works more like a house address. The variable holds an address, not the house. The characters live somewhere else in memory, and your variable just points at them.

So what happens when you write a == b for two strings? Java compares the two addresses. It never looks inside the houses. Two identical houses on different streets give you false, even though the contents match perfectly.

That single fact drives everything else. If you care about the characters, you must call a method that reads them.

2.2 The String Constant Pool

Java stores string literals in a special area called the string constant pool. Whenever the compiler sees “JavaHandsOn” in your source, it puts one copy in the pool. Every other literal with the same characters reuses that copy.

Why bother? Strings show up everywhere in real code. Sharing one copy saves a lot of memory. Since Java 7, this pool lives on the regular heap, so it grows and shrinks with the rest of your objects.

Now compare that with new String(“JavaHandsOn”). The new keyword forces a fresh object every single time. Same characters, different address.

String pooled1 = "JavaHandsOn";          // pool
String pooled2 = "JavaHandsOn";          // same pool entry
String fresh   = new String("JavaHandsOn"); // brand new object

System.out.println(pooled1 == pooled2); // Output: true
System.out.println(pooled1 == fresh);   // Output: false

Read that output again. Same characters in all three variables, yet == answers differently. The pool is exactly why beginners think == “usually works”.

2.3 Immutability Works in Your Favour

Strings in Java never change. Call toUpperCase() and you do not modify the original. You get a brand new String back, and the old one stays exactly as it was.

This matters for comparison in two ways. First, sharing pool entries stays safe. Nobody can edit a shared literal and break every other user of it.

Second, a String caches its hash code after the first calculation. That makes strings excellent HashMap keys, and it makes repeated lookups fast.

String name = "java";
name.toUpperCase();                  // result thrown away!
System.out.println(name);            // Output: java

String upper = name.toUpperCase();   // keep the result
System.out.println(upper);           // Output: JAVA

Beginners hit this all the time. They call trim() or toLowerCase(), forget to assign the result, then wonder why the comparison still fails.

For a deeper look at this behaviour, see our article on String immutability in Java.

3. The equals Method

The equals method answers the question you almost always mean to ask: do these two strings hold the same characters?

3.1 How equals Works

String overrides equals from Object. Inside, it does three quick things. It checks whether both references point to the same object. Then it confirms the other value really is a String. Finally it walks the characters, position by position.

If every character matches and the lengths match, you get true. Otherwise you get false.

package com.javahandson.string;

public class EqualsDemo {
    public static void main(String[] args) {

        String str1 = "JavaHandsOn";
        String str2 = "JavaHandsOn";
        String str3 = new String("JavaHandsOn");

        System.out.println("str1 vs str2 : " + str1.equals(str2));
        System.out.println("str1 vs str3 : " + str1.equals(str3));
        System.out.println("str2 vs str3 : " + str2.equals(str3));
    }
}
// Output:
// str1 vs str2 : true
// str1 vs str3 : true
// str2 vs str3 : true

Notice how str3 lives on the heap while str1 and str2 share a pool entry. The equals method does not care. It reads characters, so all three match.

This is what you want in ninety-nine percent of your code.

3.2 equals Is Case-Sensitive

An uppercase J and a lowercase j are different characters. So equals treats them as different, and one mismatched letter is enough to return false.

String str1 = "JavaHandsOn";
String str2 = "JAVAHandsOn";
String str3 = new String("JavaHandsOn");

System.out.println("str1 vs str2 : " + str1.equals(str2)); // Output: false
System.out.println("str1 vs str3 : " + str1.equals(str3)); // Output: true
System.out.println("str2 vs str3 : " + str2.equals(str3)); // Output: false

Here str2 shouts its first four letters. That makes it a different string, so both comparisons against it fail.

Most of the time, strict matching is exactly right. Passwords, API keys, and file checksums all need it. For usernames and email addresses you probably want the case-insensitive version instead.

3.3 equals Only Matches Other Strings

The signature takes an Object, not a String. So the compiler happily lets you pass anything at all. At runtime, equals simply returns false for any non-String argument.

String text = "42";
Integer number = 42;
StringBuilder builder = new StringBuilder("42");

System.out.println(text.equals(number));  // Output: false
System.out.println(text.equals(builder)); // Output: false
System.out.println(text.equals("42"));    // Output: true

That second line catches people out. A StringBuilder holding the very same characters still fails, because it is not a String. We fix that case in section 8.2.

3.4 Guarding Against null

Passing null into equals is perfectly safe. You get false back, no exception.

Calling equals on a null reference is a different story. That throws a NullPointerException, and it is one of the most common crashes in Java code.

String known = "Java";
String maybeNull = null;

System.out.println(known.equals(maybeNull)); // Output: false

// System.out.println(maybeNull.equals(known)); // throws NullPointerException

System.out.println("Java".equals(maybeNull)); // Output: false  (safe order)

See the trick on the last line? Put the literal first. A literal is never null, so the call can never blow up. Developers call this the Yoda comparison, and many teams treat it as a house rule.

4. The equalsIgnoreCase Method

Sometimes case simply does not matter. A user who types ADMIN means the same thing as one who types admin. That is where equalsIgnoreCase earns its keep.

4.1 Ignoring the Case

This method compares character by character, just like equals. The difference is how it decides two characters match. It folds both to uppercase and checks, then folds both to lowercase and checks again.

package com.javahandson.string;

public class IgnoreCaseDemo {
    public static void main(String[] args) {

        String str1 = "JavaHandsOn";
        String str2 = "JAVAHANDSON";
        String str3 = new String("javahandson");

        System.out.println("str1 vs str2 : " + str1.equalsIgnoreCase(str2));
        System.out.println("str1 vs str3 : " + str1.equalsIgnoreCase(str3));
        System.out.println("str2 vs str3 : " + str2.equalsIgnoreCase(str3));
    }
}
// Output:
// str1 vs str2 : true
// str1 vs str3 : true
// str2 vs str3 : true

All three strings carry the same letters in the same order. Only the shouting differs, so every check comes back true.

Null behaves the same way as before. Pass null in and you get false, with no exception.

4.2 When to Reach for It

Use it wherever a human types the value and case carries no meaning:

  • Usernames and email addresses typed into a login form
  • Yes or no answers read from the console
  • Country codes, currency codes, and similar short labels
  • File extensions, since Windows treats .TXT and .txt alike
  • Command names in a small console menu

Skip it for passwords. Case is real information there, and throwing it away weakens every password in your system.

4.3 The Locale Trap It Avoids

Many developers write a.toLowerCase().equals(b.toLowerCase()) instead. It looks equivalent. It is not.

The toLowerCase() method with no argument uses the default locale of whatever machine runs your code. In Turkish, the capital I lowercases to a dotless ı, not to i. So the same code passes in London and fails in Istanbul.

import java.util.Locale;

Locale turkish = new Locale("tr", "TR");

System.out.println("TITLE".toLowerCase(turkish));      // Output: tıtle
System.out.println("TITLE".toLowerCase(Locale.ROOT));  // Output: title

// equalsIgnoreCase never consults the locale
System.out.println("TITLE".equalsIgnoreCase("title")); // Output: true

The javadoc states plainly that equalsIgnoreCase ignores locale. That predictability is the whole point. Two extra allocations also disappear, since no lowercase copies get built.

5. The == Operator

Now we reach the operator behind most string bugs. It is not broken. It simply answers a question you rarely meant to ask.

5.1 It Compares References

The == operator asks whether two variables point to the exact same object in memory. Same address, true. Different address, false. Content plays no part.

package com.javahandson.string;

public class ReferenceDemo {
    public static void main(String[] args) {

        String str1 = "JavaHandsOn";
        String str2 = "JavaHandsOn";
        String str3 = new String("JavaHandsOn");

        System.out.println("str1 == str2 : " + (str1 == str2));
        System.out.println("str1 == str3 : " + (str1 == str3));
        System.out.println("str1.equals(str3) : " + str1.equals(str3));
    }
}
// Output:
// str1 == str2 : true
// str1 == str3 : false
// str1.equals(str3) : true

Both literals share one pool entry, so the first check passes. The new keyword built a separate object, so the second check fails even though the letters match.

5.2 Why It Sometimes Looks Right

Here lies the danger. Beginners test == with two literals, watch it print true, and conclude it works. Then real input arrives from a file, a database, or a web request. Those strings never touch the pool, and the comparison starts failing.

String literal = "yes";
String fromUser = new Scanner(System.in).nextLine(); // user types: yes

if (fromUser == literal) {
    System.out.println("never prints");
}

if (fromUser.equals(literal)) {
    System.out.println("this one prints"); // Output: this one prints
}

Nothing about the failing branch looks wrong. That is what makes the bug so nasty. It hides in code that passed every test you wrote with hard-coded values.

The rule is short. Never use == to compare string content. Not once, not as a shortcut.

5.3 Compile-Time Constants

Concatenation adds another wrinkle. When the compiler can work out the whole value at compile time, the result goes into the pool. When it cannot, the JVM builds a new object at runtime.

String a = "JavaHandsOn";
String b = "Java" + "HandsOn";        // folded at compile time

final String finalPrefix = "Java";
String c = finalPrefix + "HandsOn";   // still a constant expression

String prefix = "Java";               // not final
String d = prefix + "HandsOn";        // built at runtime

System.out.println(a == b); // Output: true
System.out.println(a == c); // Output: true
System.out.println(a == d); // Output: false
System.out.println(a.equals(d)); // Output: true

Adding one final keyword flips the answer. Removing it flips it back. Would you want your login check to depend on that? Neither would we.

5.4 The intern Method

The intern() method asks the pool for the canonical copy of a string. If the pool already holds those characters, you get that reference back. Otherwise your string joins the pool.

String pooled = "JavaHandsOn";
String heap = new String("JavaHandsOn");
String interned = heap.intern();

System.out.println(pooled == heap);     // Output: false
System.out.println(pooled == interned); // Output: true
System.out.println(heap == interned);   // Output: false

Interning has a narrow, legitimate use. If you load millions of records with a small set of repeated values, interning collapses them into shared copies and saves memory.

Do not use it to make == work. That trades a clear one-line fix for a subtle rule every future reader must remember.

6. The compareTo Method

So far we have asked yes-or-no questions. Sorting needs more. It needs to know which string comes first, and compareTo supplies that answer.

6.1 What Lexicographic Means

Lexicographic ordering is dictionary ordering, with one important twist. Java compares characters by their Unicode value, not by how a human alphabet works.

Here are the values that explain most surprises:

  • Digits 0 to 9 occupy 48 through 57
  • Capital letters A to Z sit at 65 through 90
  • Lowercase letters a to z land at 97 through 122
  • Every capital letter therefore sorts before every lowercase one
  • Accented characters such as ä live above 127, so they sort after z

That last pair of rules produces the classic complaint: “why is Zebra before apple?” Because Z is 90 and a is 97. Java is doing exactly what you asked.

6.2 The Return Value Rule

The method scans both strings from the left until it finds a position where they differ. At that point it subtracts the second character from the first and hands you the result.

What if one string simply runs out? Then no mismatch exists, so the method subtracts the lengths instead.

package com.javahandson.string;

public class CompareToDemo {
    public static void main(String[] args) {

        String str1 = "Java";
        String str2 = "HandsOn";
        String str3 = new String("java");

        System.out.println("str1 vs str2 : " + str1.compareTo(str2));
        System.out.println("str1 vs str3 : " + str1.compareTo(str3));
        System.out.println("str2 vs str3 : " + str2.compareTo(str3));
        System.out.println("prefix case  : " + str1.compareTo("JavaHandsOn"));
    }
}
// Output:
// str1 vs str2 : 2
// str1 vs str3 : -32
// str2 vs str3 : -34
// prefix case  : -7

Let us decode each number. Java versus HandsOn differs at position zero: ‘J’ is 74, ‘H’ is 72, so you get 2.

Next, Java versus java also differs at position zero: ‘J’ is 74 and ‘j’ is 106, giving -32. Every uppercase letter sits exactly 32 below its lowercase twin.

Then HandsOn versus java gives 72 minus 106, which is -34. Finally, Java is a prefix of JavaHandsOn, so the method returns 4 minus 11, or -7.

6.3 Sorting With compareTo

String implements Comparable, and compareTo is the method that interface demands. That single fact makes strings sortable everywhere in Java, with no extra work from you.

import java.util.Arrays;

String[] names = {"banana", "apple", "Cherry", "Apple"};
Arrays.sort(names);
System.out.println(Arrays.toString(names));
// Output: [Apple, Cherry, apple, banana]

Look at that result. Every capitalised word came first, and the alphabet got shuffled. Nobody wants a contact list ordered like that.

Sorting a TreeMap, a TreeSet, or a stream with sorted() all follow the same rule, because they all lean on compareTo by default.

6.4 Check the Sign, Not the Number

A frequent bug looks like this: if (a.compareTo(b) == 1). The author assumed the method returns 1, 0, or -1. It does not, as our -32 example proved.

String a = "Java";
String b = "HandsOn";

if (a.compareTo(b) == 1) {
    System.out.println("wrong test");     // never runs, value is 2
}

if (a.compareTo(b) > 0) {
    System.out.println("right test");     // Output: right test
}

Always test the sign with > 0, < 0, or == 0. The Comparable contract only promises a sign, so any other comparator you plug in later stays compatible.

7. The compareToIgnoreCase Method

This method does the same job as compareTo, minus the case sensitivity. It gives you the ordering humans actually expect.

7.1 Case-Insensitive Ordering

Internally it folds each character before comparing, so J and j count as the same letter. Everything else behaves exactly as before.

package com.javahandson.string;

public class CompareIgnoreCaseDemo {
    public static void main(String[] args) {

        String str1 = "Java";
        String str2 = "HandsOn";
        String str3 = new String("java");

        System.out.println("str1 vs str2 : " + str1.compareToIgnoreCase(str2));
        System.out.println("str1 vs str3 : " + str1.compareToIgnoreCase(str3));
        System.out.println("str2 vs str3 : " + str2.compareToIgnoreCase(str3));
    }
}
// Output:
// str1 vs str2 : 2
// str1 vs str3 : 0
// str2 vs str3 : -2

The middle line is the interesting one. Plain compareTo gave -32 for that same pair. Folding the case makes both strings identical, so you get 0.

The last line shrank from -34 to -2. After folding, ‘h’ is 104 and ‘j’ is 106, and the gap closes to two.

7.2 Sorting Names the Friendly Way

You rarely call compareToIgnoreCase by hand. Java ships a ready-made comparator that wraps it: String.CASE_INSENSITIVE_ORDER.

import java.util.Arrays;

String[] names = {"banana", "apple", "Cherry", "Apple"};

Arrays.sort(names, String.CASE_INSENSITIVE_ORDER);
System.out.println(Arrays.toString(names));
// Output: [apple, Apple, banana, Cherry]

Compare that with the earlier output. This ordering reads like a real index, which is what a user expects to see on screen.

Notice that apple kept its place ahead of Apple. The sort treats them as equal, and Java’s object sort is stable, so their original order survives.

8. Other Ways to Compare Strings

The big five cover most cases. A handful of extras cover the rest, and knowing them saves you from writing clumsy workarounds.

8.1 Objects.equals for null Safety

The Objects.equals helper handles nulls on both sides. Two nulls count as equal. One null gives false. Otherwise it delegates to equals.

import java.util.Objects;

String a = null;
String b = "Java";

System.out.println(Objects.equals(a, b));    // Output: false
System.out.println(Objects.equals(a, null)); // Output: true
System.out.println(Objects.equals(b, "Java")); // Output: true

Reach for this whenever both sides might be null, such as comparing two fields loaded from a database row.

8.2 contentEquals for StringBuilder

Remember the StringBuilder problem from section 3.3? The contentEquals method solves it. It accepts any CharSequence and compares the characters directly.

StringBuilder builder = new StringBuilder("JavaHandsOn");
String text = "JavaHandsOn";

System.out.println(text.equals(builder));        // Output: false
System.out.println(text.contentEquals(builder)); // Output: true

You can also flip the problem around and call builder.toString() first. That allocates a new String, though, so contentEquals stays the leaner choice. Our guides on StringBuilder in Java and StringBuffer in Java go deeper on those classes.

8.3 startsWith, endsWith, and regionMatches

Often you only care about part of a string. Three methods cover that neatly:

  • startsWith(prefix) checks the opening characters
  • Use endsWith(suffix) for file extensions and similar tails
  • With regionMatches(…) you compare a slice of one string against a slice of another
String file = "notes.TXT";
System.out.println(file.endsWith(".txt"));               // Output: false
System.out.println(file.toLowerCase().endsWith(".txt")); // Output: true

String path = "report_2026_final.txt";
// ignoreCase, start in path, other string, start in other, length
System.out.println(path.regionMatches(true, 7, "2026", 0, 4)); // Output: true

That regionMatches overload takes an ignore-case flag as its first argument. It also avoids building substrings, which keeps tight loops fast.

8.4 matches for Pattern Checks

When the shape of the text matters more than its exact letters, use matches. It takes a regular expression and tests the whole string against it.

String code1 = "IN-2026";
String code2 = "in-2026";

System.out.println(code1.matches("[A-Z]{2}-\\d{4}"));  // Output: true
System.out.println(code2.matches("[A-Z]{2}-\\d{4}"));  // Output: false
System.out.println(code2.matches("(?i)[A-Z]{2}-\\d{4}")); // Output: true

The (?i) flag at the front switches the pattern to case-insensitive mode. Keep in mind that matches compiles the pattern on every call, so cache a Pattern object inside hot loops.

8.5 Strings Inside a switch

Since Java 7 you can switch on a String. Under the hood, the compiler uses hashCode() to pick a candidate branch, then confirms with equals().

String command = "start";

switch (command) {
    case "start" -> System.out.println("Starting up");
    case "stop"  -> System.out.println("Shutting down");
    default      -> System.out.println("Unknown command");
}
// Output: Starting up

Two things follow from that. Matching stays case-sensitive, so normalise your input before the switch. And a null value throws a NullPointerException in a classic switch, so check for null first.

9. Choosing the Right Method

Five methods, plus a few extras, can feel like a lot. One table and one short decision path settle it.

9.1 The Cheat Sheet

Method Compares Case? Null argument
equals Content Yes Returns false
equalsIgnoreCase Content No Returns false
== Reference n/a No exception
compareTo Order Yes Throws NPE
compareToIgnoreCase Order No Throws NPE
Objects.equals Content Yes Safe both sides
contentEquals Content Yes Throws NPE

The Case? column says whether the method treats upper and lower case as different. The last column says what happens when you pass null as the argument.

Pin that table somewhere. The null column alone prevents a whole family of production crashes. Note that the first two methods return a boolean, while compareTo and compareToIgnoreCase return an int you read by sign.

9.2 A Simple Decision Path

Work through these questions in order and you will land on the right method every time:

  1. Do you need an ordering for sorting? If yes, jump to step 5.
  2. Might either side be null? Then call Objects.equals and stop.
  3. Is one side a StringBuilder or another CharSequence? Use contentEquals.
  4. Does case matter? Yes means equals, no means equalsIgnoreCase.
  5. For sorting, pick String.CASE_INSENSITIVE_ORDER when users read the output.
  6. Keep plain compareTo for machine ordering, like sorted keys or binary search.

Notice that == never appears. That is deliberate. Comparing string content with == has no place in application code.

10. Unicode, Accents, and Locale

Everything so far assumed plain English text. Real applications handle names, cities, and product titles from everywhere, and that raises two extra issues.

10.1 Same Word, Different Characters

Unicode often offers two ways to spell an accented letter. You can use one composed character, or a plain letter followed by a combining accent. Both look identical on screen. Neither matches the other under equals.

import java.text.Normalizer;

String composed   = "caf\u00e9";    // cafe, with e-acute as ONE character
String decomposed = "cafe\u0301";   // plain e plus a combining accent

System.out.println(composed.length());          // Output: 4
System.out.println(decomposed.length());        // Output: 5
System.out.println(composed.equals(decomposed));// Output: false

String n1 = Normalizer.normalize(composed, Normalizer.Form.NFC);
String n2 = Normalizer.normalize(decomposed, Normalizer.Form.NFC);
System.out.println(n1.equals(n2));              // Output: true

Normalising both sides to the same form fixes it. Do this once at the edge of your system, right where text arrives, rather than at every comparison.

10.2 Sorting With Collator

Ordering has a similar problem. Because ä sits at Unicode 228, compareTo pushes it past z. A German reader expects it right next to a.

import java.text.Collator;
import java.util.Locale;

System.out.println("äpfel".compareTo("zebra"));  // Output: 106  (positive!)

Collator german = Collator.getInstance(Locale.GERMAN);
System.out.println(german.compare("äpfel", "zebra") < 0); // Output: true

A Collator knows the rules of a language. Use one whenever a human reads the sorted list. Stick with compareTo for internal keys, where speed matters and nobody sees the order.

11. Common Mistakes and Pitfalls

These six mistakes account for most string comparison bugs we have seen in real code reviews.

11.1 Using == for Content

This one tops the list and always will. The code passes your quick test with two literals, then fails the moment real data arrives.

// Wrong
if (role == "admin") { ... }

// Right
if ("admin".equals(role)) { ... }

Turn on your IDE’s warning for reference comparison of strings. IntelliJ and Eclipse both flag it, and that alone catches the mistake before it ships.

11.2 Calling equals on a null Reference

A value from a map, a database column, or a JSON field can arrive as null. Calling any method on it crashes immediately.

String role = map.get("role"); // may be null

// Risky
if (role.equals("admin")) { ... }        // NullPointerException

// Safe
if ("admin".equals(role)) { ... }        // false when role is null

// Also safe
if (Objects.equals(role, "admin")) { ... }

11.3 Forgetting to Trim User Input

Users paste text with stray spaces. A trailing space makes “admin “ a completely different string from “admin”, and equals reports false.

String typed = "  Admin  ";

System.out.println(typed.equalsIgnoreCase("admin"));        // Output: false
System.out.println(typed.trim().equalsIgnoreCase("admin")); // Output: true
System.out.println(typed.strip().equalsIgnoreCase("admin"));// Output: true

Since Java 11, strip() beats trim(). The older method only removes characters up to code 32, while strip() understands the full Unicode definition of whitespace.

11.4 Comparing compareTo Against 1

We covered this in section 6.4, and it deserves a second mention. Testing for exactly 1 or -1 fails for most inputs, because the method returns a character difference.

Write > 0 and < 0 instead. Your future self will thank you when someone swaps in a custom comparator.

11.5 Comparing a String to a StringBuilder

The compiler accepts text.equals(builder) without a murmur, since equals takes an Object. At runtime the answer is always false, whatever the characters say.

Call contentEquals instead, or convert the builder with toString() before you compare.

11.6 Rolling Your Own Case-Insensitive Sort

A tempting shortcut looks like this: (x, y) -> x.toLowerCase().compareTo(y.toLowerCase()). It allocates two throwaway strings on every single comparison, and it drags the default locale into your sort order.

// Wasteful and locale-dependent
list.sort((x, y) -> x.toLowerCase().compareTo(y.toLowerCase()));

// Clean, allocation-free, locale-independent
list.sort(String.CASE_INSENSITIVE_ORDER);

12. A Practical Walkthrough

Time to put the pieces together in one small program that you could drop into a real project.

12.1 The Problem

We want a tiny user directory. It must answer two questions. Is this name registered? And what does the full list look like when we print it?

Real input is messy, so the checker has to handle four things:

  • A null name, without crashing
  • Extra spaces around what the user typed
  • Any mix of uppercase and lowercase letters
  • A display order that reads naturally to a person

12.2 The Code

package com.javahandson.string;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class UserDirectory {

    private static final List<String> USERS =
            Arrays.asList("Suraj", "amit", "Neha", "Ravi");

    static boolean isRegistered(String input) {
        if (input == null) {
            return false;          // never call a method on null
        }
        String name = input.strip(); // drop stray spaces first
        for (String user : USERS) {
            if (user.equalsIgnoreCase(name)) {
                return true;       // content check, case ignored
            }
        }
        return false;
    }

    static List<String> sortedForDisplay() {
        List<String> copy = new ArrayList<>(USERS);
        copy.sort(String.CASE_INSENSITIVE_ORDER);
        return copy;
    }

    public static void main(String[] args) {
        System.out.println(isRegistered("suraj"));   // Output: true
        System.out.println(isRegistered("  AMIT ")); // Output: true
        System.out.println(isRegistered("Kiran"));   // Output: false
        System.out.println(isRegistered(null));      // Output: false

        System.out.println(sortedForDisplay());
        // Output: [amit, Neha, Ravi, Suraj]
    }
}

12.3 Reading the Result

Four lines of defence do all the work here. The null guard comes first, so nothing downstream can crash. Then strip() cleans the input before any comparison touches it.

Next, equalsIgnoreCase matches suraj against the stored Suraj. A plain equals would have rejected both of the first two lookups.

Finally, String.CASE_INSENSITIVE_ORDER gives a display list that reads alphabetically. Plain compareTo would have printed [Neha, Ravi, Suraj, amit], pushing the lowercase entry to the end.

Swap any one of those choices and the program breaks in a way a user would notice. That is the whole lesson of this article in a single class.

13. Interview Questions

Q: What is the difference between == and equals() for Java strings?

A: The == operator compares references, so it asks whether both variables point to the same object in memory. The equals() method compares the actual characters. For string content, always use equals().

Q: Why does == sometimes return true for two different String variables?

A: Java keeps string literals in the constant pool and reuses one copy for identical literals. So two literals with the same characters share an address, and == gives true. Build a string with new, or read it from input, and the same test gives false.

Q: How do I compare two strings while ignoring case in Java?

A: Call equalsIgnoreCase() for a yes-or-no check, and compareToIgnoreCase() when you need an ordering. Avoid a.toLowerCase().equals(b.toLowerCase()), because toLowerCase() uses the default locale and gives different answers on a Turkish machine.

Q: What does the compareTo method return in Java?

A: It scans both strings and, at the first differing position, returns the first character minus the second. If one string is a prefix of the other, it returns the difference in lengths. Zero means the strings match. Test the sign with > 0 or < 0, never against 1.

Q: Why is “admin”.equals(role) safer than role.equals(“admin”)?

A: A literal can never be null, so the first form never throws a NullPointerException. If role is null, it simply returns false. The second form crashes. Developers call this the Yoda comparison.

Q: How do I compare two strings when both might be null?

A: Use Objects.equals(a, b) from java.util. It treats two nulls as equal, returns false when only one side is null, and otherwise delegates to String.equals(). No null check of your own is needed.

Q: What is the difference between compareTo and compareToIgnoreCase?

A: Both give an ordering, but compareTo uses raw Unicode values, so every uppercase letter sorts before every lowercase one. compareToIgnoreCase folds the case first, which produces the alphabetical order a reader expects. For sorting, prefer String.CASE_INSENSITIVE_ORDER.

Q: Can I compare a String with a StringBuilder using equals()?

A: No. String.equals() returns false for anything that is not a String, even when the characters match. Call text.contentEquals(builder) instead, or convert the builder with toString() before comparing.

Q: Does a switch statement on a String use == or equals()?

A: It uses equals(). The compiler first switches on hashCode() to pick a candidate branch, then confirms the match with equals(). Matching stays case-sensitive, and a null value throws a NullPointerException in a classic switch.

Q: Why do two strings that look identical return false from equals()?

A: Three causes explain almost every case. One side has leading or trailing whitespace, so call strip() first. The case differs, so use equalsIgnoreCase(). Or an accented letter uses a different Unicode form, which Normalizer.normalize() fixes.

Q: Should I use intern() to make == work on strings?

A: No. The intern() method exists to save memory when you hold millions of repeated values. Using it so that == compares content makes your code depend on a hidden rule that every future reader must remember. Call equals() instead.

14. Conclusion

Let us wrap up what we covered. A String variable holds an address, not the characters, so == compares addresses and answers a question you almost never mean to ask.

For content, equals is your default. Switch to equalsIgnoreCase whenever a human typed the value and the shouting carries no meaning.

For ordering, compareTo gives raw Unicode order, which puts every capital letter ahead of every lowercase one. When a person reads the list, sort with String.CASE_INSENSITIVE_ORDER instead.

Keep the small helpers close at hand too. Objects.equals survives nulls on both sides, contentEquals handles a StringBuilder, and Normalizer rescues accented text.

Clean up your input before you compare it. Strip the whitespace, settle on a case, and normalise the Unicode. Do that once at the edge, and every comparison after it becomes simple.

Further Reading

Leave a Comment