Java String Equality: Choose equals(), == or Objects.equals()
Distinguish Java String identity from content and test nullable comparisons with a runnable companion.
Two Java variables can refer to different String objects that contain the same text. A comparison can therefore answer either of two questions: “Are these references the same?” or “Do these strings contain the same characters?” Pick the question before picking the operator.
This lesson uses a small local program with fictional labels. It does not require a database, framework, account or internet connection. Basic variables, method calls and boolean values are enough to follow it.
Start with distinct objects, not two identical literals
For String references, == checks identity, while String.equals checks content. This distinction is easier to see when the example deliberately creates two objects:
String left = new String("Mango");
String right = new String("Mango");
String alias = left;
System.out.println(left == right); // false
System.out.println(left.equals(right)); // true
System.out.println(left == alias); // true
These statements belong inside a method such as main; the complete runnable companion below supplies that wrapper.
The two new expressions are intentional teaching devices. They make the distinct-object case explicit; ordinary application code does not need to wrap every literal in new String. The alias assignment does not copy a String object. It gives another variable the same reference.
The A and B labels identify objects symbolically. They are not memory addresses and do not claim a particular JVM storage layout.
Compare the question each expression answers
| Expression | Question | Result in the example |
|---|---|---|
| left == right | Same reference? | false |
| left.equals(right) | Equal String content? | true |
| left == alias | Same reference? | true |
| Objects.equals(left, right) | Equal with null handling? | true |
The final expression needs java.util.Objects imported. For non-null String operands it gives the content comparison needed here.
Do not turn this into a blanket rule that == never returns true for strings. The alias case already disproves that. Likewise, a successful identity comparison in a small example does not demonstrate that identity is the right test for independently obtained text.
Why a literal-only experiment can hide the distinction
A lesson that compares the same string literal twice can return true with == because the references can be shared. That result answers the identity question; it does not change what the operator tests.
Use the distinct-object example to diagnose your assumption instead of changing code until one test happens to pass. String pooling is not a substitute for a content comparison. This guide does not depend on a particular optimization or benchmark.
When reading a failing test, record both the expected business value and the expression used to compare it. “Both variables print Mango” is evidence about displayed text, not proof that two references are identical.
Make the null policy explicit
A null receiver cannot execute an instance method. With a variable declared as String missing = null, missing.equals(left) throws NullPointerException rather than returning false.
Objects.equals can handle nullable inputs. In this String example, two nulls compare equal, one null compares unequal to a non-null string, and two non-null strings are compared using equals.
| Inputs a and b | Objects.equals(a, b) |
|---|---|
| null and null | true |
| null and a non-null String | false |
| a non-null String and null | false |
| Distinct Strings containing Mango | true |
A fixed non-null literal receiver, such as "Mango".equals(missing), is another useful form when comparing against one constant. It returns false for a null argument. A separate missing == null check is also appropriate when the question really is whether a value is absent.
Null-safe equality does not decide whether two missing values should count as a valid match in your application. For example, if a label is required, validate that requirement separately before comparing labels. Avoid silently treating null, empty text and whitespace-only text as interchangeable.
Run the companion and check the output
Save the supplied EqualityDemo.java companion and run it with a Java 17 JDK:
java EqualityDemo.java
Source-file launch needs a JDK with compiler support; a runtime-only installation may not provide it. The alternative with a full compiler installation is javac EqualityDemo.java followed by java EqualityDemo.
The companion repeats the three opening comparisons and performs 15 explicit checks. It throws AssertionError on a mismatch without relying on the optional -ea flag. It deliberately catches one expected NullPointerException to test the null-receiver case.
Expected output:
left == right: false
left.equals(right): true
left == alias: true
Objects.equals(null, null): true
Objects.equals(null, left): false
missing.equals(left): expected NullPointerException caught
PASS: 15 explicit checks
Do not catch NullPointerException around ordinary comparisons as a repair. The catch in this tiny program is a test of known behavior, not an application error-handling strategy.
Test the boundaries of the comparison
The companion also checks a changed letter case, a trailing space and a StringBuilder with similar displayed text. String.equals is not a general “looks the same” operation: it requires another String with matching content.
If a feature requires case-insensitive or normalized matching, specify that as a separate requirement and test it explicitly. Replacing equals with trimming or lowercasing everywhere can hide input problems and changes the meaning of the comparison.
Also avoid applying String's content contract automatically to every class. Objects.equals delegates to the relevant object's equals implementation; it does not invent a universal deep comparison of arbitrary object graphs.
Practise before adding framework code
Change the sample label to a different word. Predict each of the first three results, then run the class. Next, add a test in which two variables are null and another in which only one is null. State whether each test is checking identity, text or absence.
Keep this exercise separate from database and HTTP code until the model is clear. For a broader sequence, continue with the Java programming roadmap. To document expected outcomes and reproduce failures, use software testing basics.
Sources
Primary references, checked 17 October 2026: