equals() and equalsIgnoreCase()
Compare String content with equals() or ignore case with equalsIgnoreCase(), and avoid == and the NullPointerException traps.
public boolean equals(Object anObject)- Added in Java Java 1.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$anObject | Object | The 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 simplyfalsefor null. Objects.equals(a, b)when either side may be null; it istruewhen 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
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.
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] -> anonymousWhat 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.
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
// falseComparing 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.
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