equals() and equalsIgnoreCase()

Compare String content with equals() or ignore case with equalsIgnoreCase(), and avoid == and the NullPointerException traps.

Signature
public boolean equals(Object anObject)
  • Added in Java Java 1.0

Parameters

ParameterTypeDescription
$anObjectObjectThe object to compare with. null and non-String objects give false.

Return value

booleantrue if anObject is a String with exactly the same sequence of chars. equalsIgnoreCase(String) also ignores case, comparing code point by code point without a locale.

Errors

Nothing for the argument; null returns false. Calling it on a null reference throws NullPointerException.

Changelog

  • PHP Java 16equalsIgnoreCase (and regionMatches with ignoreCase true) compare Unicode code points instead of single chars, so case-insensitive matching also works for letters outside the Basic Multilingual Plane.

a.equals(b) is true when both Strings contain the same chars in the same order. a.equalsIgnoreCase(b) does the same but treats upper and lower case as equal. Both return false for a null argument instead of throwing.

Never use == for content

== tests whether two references point at the same object. Identical literals share one pooled object, so "admin" == "admin" happens to be true and tests written with literals pass. Strings read from a request, a database or built at run time are new objects, so the same comparison in production is false. Always compare text with equals.

Null safety

Calling role.equals("admin") throws when role is null. Two safe forms:

  • Constant first: "admin".equals(role) is simply false for null.
  • Objects.equals(a, b) when either side may be null; it is true when both are null.

A switch on a String uses equals internally and throws NullPointerException on null, unless you add a case null label (Java 21 and later).

What equalsIgnoreCase does and does not do

It compares code point by code point (char by char before Java 16) using Character.toUpperCase and Character.toLowerCase, with no locale. That makes it predictable for header names and protocol tokens. It does not expand chars, so "STRASSE" and "straße" are not equal, and it does not normalise accents. For sorting without case, use compareToIgnoreCase or String.CASE_INSENSITIVE_ORDER.

Secrets

equals stops at the first difference, so its timing can reveal how much of a guessed token was right. For API keys, webhook signatures and HMACs, compare bytes with MessageDigest.isEqual, which does not stop early.

To compare a String with a StringBuilder, use contentEquals; equals returns false for any non-String.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Checking a role or scope claim from a token against the one an endpoint requires.
  • Matching HTTP header names, which are case-insensitive, with equalsIgnoreCase.
  • Verifying a webhook signature without leaking timing information.

Frequently asked questions

What is the difference between == and equals for Strings in Java?

== checks whether two references point at the same object; equals checks whether the text is the same. Strings from input, databases or run-time concatenation are separate objects, so == can be false for equal text.

How do I compare Strings when one might be null?

Put a known non-null value first, as in "admin".equals(role), or use Objects.equals(a, b), which handles null on either side.

Is equalsIgnoreCase locale-sensitive?

No. It compares each pair of code points using Character.toUpperCase and Character.toLowerCase, without any locale, so it behaves the same on every server.

More examples

Mapping a role to an access level

A switch on a String compares with equals, so the cases match by content. The explicit null check is needed because the switch would otherwise throw NullPointerException.

Example · java
String access(String role) {
    if (role == null) return "anonymous";
    return switch (role.strip().toLowerCase(Locale.ROOT)) {
        case "admin" -> "full";
        case "editor" -> "write";
        default -> "read";
    };
}

void main() {
    for (String r : Arrays.asList("Admin", " editor ", "guest", null)) {
        IO.println("[" + r + "] -> " + access(r));
    }
}
// Output:
// [Admin] -> full
// [ editor ] -> write
// [guest] -> read
// [null] -> anonymous

What equalsIgnoreCase treats as equal

It maps one code point at a time without a locale: a dotless i uppercases to I, so it matches, but sharp s is never expanded to SS and an accent is never dropped. For header names this is exactly the behaviour you want.

Example · java
void main() {
    IO.println("Content-Type".equalsIgnoreCase("content-type"));
    IO.println("STRASSE".equalsIgnoreCase("stra\u00DFe"));
    IO.println("TITLE".equalsIgnoreCase("t\u0131tle"));
    IO.println("caf\u00e9".equalsIgnoreCase("cafe"));
}
// Output:
// true
// false
// true
// false

Comparing a webhook signature safely

MessageDigest.isEqual does not return at the first mismatching byte, unlike String.equals, so response timing does not tell an attacker how many leading characters were right.

Example · java
boolean validSignature(String expected, String given) {
    if (given == null) return false;
    return MessageDigest.isEqual(
            expected.getBytes(StandardCharsets.UTF_8),
            given.getBytes(StandardCharsets.UTF_8));
}

void main() {
    String expected = "a3f9c2d1";       // fake value for the demo
    IO.println(validSignature(expected, "a3f9c2d1"));
    IO.println(validSignature(expected, "a3f9c2d0"));
    IO.println(validSignature(expected, null));
}
// Output:
// true
// false
// false

Discussion

  • Be the first to comment on this lesson.