Java equals() vs == Operator
Java equals() vs == Operator
This is the most commonly asked Java interview question for freshers — and also the most commonly misunderstood concept in daily Java development. Dozens of production bugs trace back to someone writing == where they needed equals(), or calling equals() on a null reference where Objects.equals() was the safe choice.
The confusion is understandable. For int, double, and other primitives, == compares values and works exactly as expected. For objects — including String, Integer, and every custom class — == compares memory addresses, not content. Two objects that hold identical data can return false for == simply because they are stored at different locations in memory.
The Core Rule — One Sentence
== compares what is stored in the variable. For primitives, that is the value itself. For objects, that is the memory address of the object (the reference).
Primitive variable: Object variable:
int x = 5; String s = "hello";
┌───────┐ ┌───────────┐
│ 5 │ ← x stores value │ address │ ← s stores address
└───────┘ └─────┬─────┘
│
▼
┌─────────────┐
│ "hello" │ ← actual String object
└─────────────┘
x == 5 → compares 5 == 5 → true
s == "hello" → compares address == pool address
(may be true for literals, false for new String())
== With Primitives — Works as Expected
For all eight primitive types — int, long, double, float, boolean, char, byte, short — == compares the actual stored values directly. This works exactly as most beginners expect.
1// File: PrimitiveEquality.java
2
3public class PrimitiveEquality {
4
5 public static void main(String[] args) {
6
7 // int comparison
8 int a = 100;
9 int b = 100;
10 int c = 200;
11 System.out.println("=== int ===");
12 System.out.println("a == b : " + (a == b)); // true — both store 100
13 System.out.println("a == c : " + (a == c)); // false — 100 != 200
14
15 // double comparison
16 double price1 = 99.99;
17 double price2 = 99.99;
18 System.out.println("\n=== double ===");
19 System.out.println("price1 == price2 : " + (price1 == price2)); // true
20
21 // boolean comparison
22 boolean isActive = true;
23 boolean isPending = false;
24 System.out.println("\n=== boolean ===");
25 System.out.println("isActive == true : " + (isActive == true)); // true
26 System.out.println("isPending == isActive: " + (isPending == isActive)); // false
27
28 // char comparison — compares Unicode values
29 char grade1 = 'A';
30 char grade2 = 'A';
31 char grade3 = 'B';
32 System.out.println("\n=== char ===");
33 System.out.println("grade1 == grade2 : " + (grade1 == grade2)); // true — same Unicode
34 System.out.println("grade1 == grade3 : " + (grade1 == grade3)); // false
35
36 // Floating-point precision warning
37 double x = 0.1 + 0.2;
38 double y = 0.3;
39 System.out.println("\n=== Floating-point precision ===");
40 System.out.println("0.1 + 0.2 == 0.3 : " + (x == y)); // false — precision issue!
41 System.out.println("0.1 + 0.2 = " + x); // 0.30000000000000004
42 System.out.println("Use Math.abs for double comparison: "
43 + (Math.abs(x - y) < 0.0001)); // true — safe comparison
44 }
45}Output:
=== int ===
a == b : true
a == c : false
=== double ===
price1 == price2 : true
=== boolean ===
isActive == true : true
isPending == isActive: false
=== char ===
grade1 == grade2 : true
grade1 == grade3 : false
=== Floating-point precision ===
0.1 + 0.2 == 0.3 : false
0.1 + 0.2 = 0.30000000000000004
Use Math.abs for double comparison: true
One important caveat: never use == to compare double or float values that result from arithmetic. Floating-point arithmetic introduces tiny precision errors — 0.1 + 0.2 is not exactly 0.3 in binary floating-point. Use Math.abs(a - b) < epsilon for numeric tolerance comparison.
== With Objects — Compares References, Not Content
For any object type — String, Integer, ArrayList, or a custom class — == compares memory addresses. Two different objects with identical content return false.
1// File: ObjectEquality.java
2
3public class ObjectEquality {
4
5 static class Product {
6 String name;
7 double price;
8
9 Product(String name, double price) {
10 this.name = name;
11 this.price = price;
12 }
13 }
14
15 public static void main(String[] args) {
16
17 Product p1 = new Product("Laptop", 45000.0);
18 Product p2 = new Product("Laptop", 45000.0); // identical data
19 Product p3 = p1; // same reference
20
21 System.out.println("=== Object Reference Check ===");
22 System.out.println("p1 == p2 : " + (p1 == p2)); // false — two different objects
23 System.out.println("p1 == p3 : " + (p1 == p3)); // true — same object
24 System.out.println("p2 == p3 : " + (p2 == p3)); // false
25
26 System.out.println();
27
28 // Identity hash code confirms different objects
29 System.out.println("p1 address: " + System.identityHashCode(p1));
30 System.out.println("p2 address: " + System.identityHashCode(p2)); // different
31 System.out.println("p3 address: " + System.identityHashCode(p3)); // same as p1
32
33 System.out.println();
34
35 // What == actually means for objects
36 System.out.println("=== What == means for objects ===");
37 System.out.println("p1 == p2 means: do p1 and p2 point to the same object?");
38 System.out.println("Answer: " + (p1 == p2)); // false — they are different objects
39 System.out.println();
40 System.out.println("p1.name.equals(p2.name): " + p1.name.equals(p2.name)); // true
41 System.out.println("p1.price == p2.price : " + (p1.price == p2.price)); // true (primitive)
42 }
43}Output:
=== Object Reference Check ===
p1 == p2 : false
p2 == p3 : false
p1 == p3 : true
p1 address: 1173230247
p2 address: 856419764
p3 address: 1173230247
=== What == means for objects ===
p1 == p2 means: do p1 and p2 point to the same object?
Answer: false
p1.name.equals(p2.name): true
p1.price == p2.price : true (primitive)
p3 = p1 does not copy the object — it copies the reference (memory address). Both p1 and p3 now point to the same Product object. p2 is a separate object with the same field values. The identity hash codes confirm this.
== With Strings — The Tricky Case
Strings behave in a way that confuses beginners most: == sometimes returns true and sometimes false for strings with identical content — depending on how the string was created.
1// File: StringEquality.java
2
3public class StringEquality {
4
5 public static void main(String[] args) {
6
7 // Case 1 — String literals — stored in String Pool — same object reused
8 String s1 = "hello";
9 String s2 = "hello";
10
11 System.out.println("=== String Literals (Pool) ===");
12 System.out.println("s1 == s2 : " + (s1 == s2)); // true — same pool object
13 System.out.println("s1.equals(s2) : " + s1.equals(s2)); // true
14
15 System.out.println();
16
17 // Case 2 — new String() — always creates a new heap object
18 String s3 = new String("hello");
19 String s4 = new String("hello");
20
21 System.out.println("=== new String() (Heap) ===");
22 System.out.println("s3 == s4 : " + (s3 == s4)); // false — different objects
23 System.out.println("s3.equals(s4) : " + s3.equals(s4)); // true — same content
24 System.out.println("s1 == s3 : " + (s1 == s3)); // false — pool vs heap
25
26 System.out.println();
27
28 // Case 3 — runtime-built strings — always new heap objects
29 String base = "hel";
30 String concat = base + "lo"; // built at runtime — new heap object
31
32 System.out.println("=== Runtime Concatenation (Heap) ===");
33 System.out.println("s1 == concat : " + (s1 == concat)); // false — concat is on heap
34 System.out.println("s1.equals(concat): " + s1.equals(concat)); // true — same content
35
36 System.out.println();
37
38 // The lesson — NEVER use == for string content comparison
39 // Strings from database, user input, API — always heap objects
40 System.out.println("=== The Real-World Problem ===");
41 String fromDB = fetchFromDatabase(); // always a new heap object
42 String expected = "active";
43
44 System.out.println("fromDB == expected : " + (fromDB == expected)); // false — bug!
45 System.out.println("expected.equals(fromDB): " + expected.equals(fromDB)); // true — correct
46 }
47
48 // Simulates reading a String from a database or API response
49 static String fetchFromDatabase() {
50 return new String("active"); // always a new heap object in reality
51 }
52}Output:
=== String Literals (Pool) ===
s1 == s2 : true
s1.equals(s2) : true
=== new String() (Heap) ===
s3 == s4 : false
s3.equals(s4) : true
s1 == s3 : false
=== Runtime Concatenation (Heap) ===
s1 == concat : false
s1.equals(concat): true
=== The Real-World Problem ===
fromDB == expected : false
fromDB.equals(fromDB) : true
This is exactly why == for Strings fails in production: values from databases, files, APIs, and user input are always new heap objects — they are never in the String pool. equals() works correctly for all of them because it compares content, not address.
== With Wrapper Classes — The Integer Cache Trap
Wrapper classes (Integer, Long, Double) are objects. == compares their references. However, Java caches Integer values from -128 to 127 in a special pool — similar to the String pool — which makes == return true for small integers and false for larger ones. This is one of the most famous Java interview traps.
1// File: WrapperEquality.java
2
3public class WrapperEquality {
4
5 public static void main(String[] args) {
6
7 // Integer cache — -128 to 127 are cached
8 Integer a = 100;
9 Integer b = 100;
10 Integer c = 200;
11 Integer d = 200;
12
13 System.out.println("=== Integer Cache Trap ===");
14 System.out.println("a == b (value 100): " + (a == b)); // true — cached range!
15 System.out.println("c == d (value 200): " + (c == d)); // false — outside cache
16 System.out.println();
17 System.out.println("a.equals(b) : " + a.equals(b)); // true — always reliable
18 System.out.println("c.equals(d) : " + c.equals(d)); // true — always reliable
19
20 System.out.println();
21
22 // Why the cache exists — performance optimisation
23 // Autoboxing frequently used small integers reuses the same objects
24 System.out.println("=== Why This Happens ===");
25 System.out.println("Integer.valueOf(100) == Integer.valueOf(100): "
26 + (Integer.valueOf(100) == Integer.valueOf(100))); // true — cached
27 System.out.println("Integer.valueOf(200) == Integer.valueOf(200): "
28 + (Integer.valueOf(200) == Integer.valueOf(200))); // false — not cached
29
30 System.out.println();
31
32 // Same trap with Long
33 Long x = 50L;
34 Long y = 50L;
35 Long p = 200L;
36 Long q = 200L;
37
38 System.out.println("=== Long Cache ===");
39 System.out.println("50L == 50L : " + (x == y)); // true — in cache range
40 System.out.println("200L == 200L : " + (p == q)); // false — outside cache
41
42 System.out.println();
43
44 // Safe comparison — always use equals() for wrapper types
45 System.out.println("=== Always Use equals() for Wrappers ===");
46 Integer score1 = 500;
47 Integer score2 = 500;
48 System.out.println("score1 == score2 : " + (score1 == score2)); // false — unreliable
49 System.out.println("score1.equals(score2) : " + score1.equals(score2)); // true — reliable
50
51 // Unboxing — comparing wrapper to primitive uses == safely
52 int primitive = 500;
53 System.out.println("score1 == primitive : " + (score1 == primitive)); // true — auto-unboxed
54 }
55}Output:
=== Integer Cache Trap ===
a == b (value 100): true
c == d (value 200): false
a.equals(b) : true
c.equals(d) : true
=== Why This Happens ===
Integer.valueOf(100) == Integer.valueOf(100): true
Integer.valueOf(200) == Integer.valueOf(200): false
=== Long Cache ===
50L == 50L : true
200L == 200L : false
=== Always Use equals() for Wrappers ===
score1 == score2 : false
score1.equals(score2) : true
score1 == primitive : true
The Integer cache is why interview code like Integer a = 127; Integer b = 127; System.out.println(a == b) prints true, but Integer a = 128; Integer b = 128; prints false. This is a guaranteed interview question — the only safe answer is: never use == for wrapper objects.
equals() — Object Content Comparison
equals() is a method inherited from java.lang.Object. The default implementation in Object behaves exactly like == — reference comparison. To make it compare content, you must override it in your class.
1// File: EqualsOverrideDemo.java
2
3import java.util.Objects;
4
5public class EqualsOverrideDemo {
6
7 // Without override — uses Object.equals() which is the same as ==
8 static class ProductWithout {
9 String sku;
10 ProductWithout(String sku) { this.sku = sku; }
11 }
12
13 // With override — compares content correctly
14 static class Product {
15 private final String sku;
16 private final String name;
17 private final double price;
18
19 Product(String sku, String name, double price) {
20 this.sku = sku;
21 this.name = name;
22 this.price = price;
23 }
24
25 @Override
26 public boolean equals(Object obj) {
27 if (this == obj) return true; // Step 1: same reference
28 if (obj == null) return false; // Step 2: null check
29 if (!(obj instanceof Product other)) return false; // Step 3: type check
30 return Objects.equals(sku, other.sku); // Step 4: field comparison
31 // Two Products are "equal" if they have the same SKU
32 }
33
34 @Override
35 public int hashCode() {
36 return Objects.hash(sku); // must match equals — same field
37 }
38
39 @Override
40 public String toString() {
41 return "Product{" + sku + ", " + name + ", Rs." + price + "}";
42 }
43 }
44
45 public static void main(String[] args) {
46
47 // Without override — equals() behaves like ==
48 ProductWithout pw1 = new ProductWithout("SKU-001");
49 ProductWithout pw2 = new ProductWithout("SKU-001");
50
51 System.out.println("=== Without Override ===");
52 System.out.println("pw1 == pw2 : " + (pw1 == pw2)); // false
53 System.out.println("pw1.equals(pw2) : " + pw1.equals(pw2)); // false — same as ==
54
55 System.out.println();
56
57 // With override — equals() compares SKU content
58 Product p1 = new Product("SKU-001", "Laptop", 45000.0);
59 Product p2 = new Product("SKU-001", "Laptop", 45000.0); // same SKU
60 Product p3 = new Product("SKU-002", "Monitor", 18000.0); // different SKU
61
62 System.out.println("=== With Override ===");
63 System.out.println("p1 == p2 : " + (p1 == p2)); // false — different objects
64 System.out.println("p1.equals(p2) : " + p1.equals(p2)); // true — same SKU
65 System.out.println("p1.equals(p3) : " + p1.equals(p3)); // false — different SKU
66 System.out.println("p1.equals(null): " + p1.equals(null)); // false — null safe
67
68 System.out.println();
69
70 // HashSet deduplication depends on correct equals() + hashCode()
71 java.util.Set<Product> catalogue = new java.util.HashSet<>();
72 catalogue.add(p1);
73 catalogue.add(p2); // same SKU as p1 — should not be added
74 catalogue.add(p3);
75
76 System.out.println("Unique products in catalogue: " + catalogue.size()); // 2
77 }
78}Output:
=== Without Override ===
pw1 == pw2 : false
pw1.equals(pw2) : false
=== With Override ===
p1 == p2 : false
p1.equals(p2) : true
p1.equals(p3) : false
p1.equals(null): false
Unique products in catalogue: 2
HashSet uses equals() and hashCode() together. Without overriding both, HashSet treats every object as unique regardless of content — which silently allows duplicates in a catalogue that should contain one entry per SKU.
== vs equals() — Complete Comparison Table
| Aspect | == | equals() |
|---|---|---|
| Type | Language operator | Method from java.lang.Object |
| For primitives | Compares values directly | Not applicable — primitives have no methods |
| For objects | Compares memory addresses (references) | Compares logical content — depends on override |
| Default object behaviour | Reference equality | Same as == if not overridden in the class |
| Can be overridden | No — operator is fixed | Yes — every class can define its own logic |
| Null safety | null == null is true | Throws NullPointerException if called on null |
| String literals | May return true (pool reuse) | Always returns true for equal content |
| Strings from DB / API | Returns false (heap objects) | Returns true for equal content |
| Integer values 127 or below | Returns true (cached) | Always returns true for equal values |
| Integer values above 127 | Returns false (heap) | Always returns true for equal values |
Used in HashSet / HashMap | Never — collections use equals() | Always — alongside hashCode() |
| When to use | Reference identity, null checks, primitives | Content equality for objects |
| Reliable for content | No — unpredictable for objects | Yes — always reliable when correctly overridden |
Null Handling — equals() vs == vs Objects.equals()
1// File: NullHandlingDemo.java
2
3import java.util.Objects;
4
5public class NullHandlingDemo {
6
7 public static void main(String[] args) {
8
9 String a = "hello";
10 String b = null;
11 String c = null;
12
13 // == with null — always safe
14 System.out.println("=== == with null ===");
15 System.out.println("b == null : " + (b == null)); // true
16 System.out.println("a == null : " + (a == null)); // false
17 System.out.println("b == c : " + (b == c)); // true — both null
18
19 System.out.println();
20
21 // equals() with null — NPE if called on null reference
22 System.out.println("=== equals() with null ===");
23 try {
24 System.out.println(b.equals("hello")); // NPE — b is null
25 } catch (NullPointerException e) {
26 System.out.println("b.equals('hello') → NullPointerException!");
27 }
28
29 // Safe pattern — constant on left
30 System.out.println("'hello'.equals(b): " + "hello".equals(b)); // false — no NPE
31
32 System.out.println();
33
34 // Objects.equals() — null-safe on BOTH sides
35 System.out.println("=== Objects.equals() — null-safe both sides ===");
36 System.out.println("Objects.equals(a, 'hello'): " + Objects.equals(a, "hello")); // true
37 System.out.println("Objects.equals(b, 'hello'): " + Objects.equals(b, "hello")); // false
38 System.out.println("Objects.equals(b, c) : " + Objects.equals(b, c)); // true — both null
39 System.out.println("Objects.equals(a, null) : " + Objects.equals(a, null)); // false
40
41 System.out.println();
42
43 // When to use each
44 System.out.println("=== Choosing the right tool ===");
45 System.out.println("Use == null : to check if a reference is null");
46 System.out.println("Use equals() : when you KNOW left side is non-null");
47 System.out.println("Use Objects.equals(): when EITHER side could be null");
48 }
49}Output:
=== == with null ===
b == null : true
a == null : false
b == c : true
=== equals() with null ===
b.equals('hello') → NullPointerException!
'hello'.equals(b): false
=== Objects.equals() — null-safe both sides ===
Objects.equals(a, 'hello'): true
Objects.equals(b, 'hello'): false
Objects.equals(b, c) : true
Objects.equals(a, null) : false
=== Choosing the right tool ===
Use == null : to check if a reference is null
Use equals() : when you KNOW left side is non-null
Use Objects.equals(): when EITHER side could be null
Real-World Example — E-Commerce Order Validation
The Business Problem
An order management system at a platform like Flipkart or Meesho processes orders through a validation pipeline. Order IDs must match exactly (case-sensitive equals()). Payment status from the payment gateway — which comes as a new String from an API call — must be checked correctly (not with ==). Two Order objects must be checked for equality based on their order ID field. And Integer order counts from a summary map must be compared safely without the Integer cache trap.
1// File: Order.java
2
3import java.util.Objects;
4
5public class Order {
6
7 private final String orderId;
8 private final String customerId;
9 private final double amount;
10 private String paymentStatus;
11
12 public Order(String orderId, String customerId, double amount) {
13 this.orderId = orderId;
14 this.customerId = customerId;
15 this.amount = amount;
16 this.paymentStatus = "PENDING";
17 }
18
19 public String getOrderId() { return orderId; }
20 public String getCustomerId() { return customerId; }
21 public double getAmount() { return amount; }
22 public String getPaymentStatus() { return paymentStatus; }
23
24 public void setPaymentStatus(String status) {
25 this.paymentStatus = status;
26 }
27
28 // Two orders are equal when they have the same orderId
29 @Override
30 public boolean equals(Object obj) {
31 if (this == obj) return true;
32 if (!(obj instanceof Order other)) return false;
33 return Objects.equals(orderId, other.orderId);
34 }
35
36 @Override
37 public int hashCode() {
38 return Objects.hash(orderId);
39 }
40
41 @Override
42 public String toString() {
43 return "Order{id=" + orderId
44 + ", cust=" + customerId
45 + ", amt=Rs." + amount
46 + ", status=" + paymentStatus + "}";
47 }
48}1// File: OrderValidator.java
2
3import java.util.Objects;
4
5public class OrderValidator {
6
7 // Validates that an order ID from API matches the expected format
8 // Status comes from payment gateway — always a new heap String
9 public static boolean validatePaymentStatus(String gatewayResponse,
10 String expectedStatus) {
11 // WRONG — gatewayResponse is a new String from API — == will fail
12 // if (gatewayResponse == expectedStatus) { ... }
13
14 // CORRECT — equals() compares content, not address
15 return expectedStatus.equals(gatewayResponse);
16 }
17
18 // Checks whether two Order objects represent the same order
19 public static boolean isSameOrder(Order order1, Order order2) {
20 // == would only be true if they are the literally the same object
21 // equals() checks the orderId field as defined in Order.equals()
22 return Objects.equals(order1, order2);
23 }
24
25 // Checks whether order count meets a threshold
26 // Returns safe comparison avoiding Integer cache issues
27 public static boolean meetsMinimumCount(Integer actual, int required) {
28 if (actual == null) return false;
29
30 // WRONG — == on Integer can fail above 127
31 // if (actual == required) { ... }
32
33 // CORRECT — unbox for comparison with a primitive int
34 return actual >= required; // auto-unboxing — safe for all values
35 }
36}1// File: OrderSystemDemo.java
2
3import java.util.HashMap;
4import java.util.Map;
5
6public class OrderSystemDemo {
7
8 public static void main(String[] args) {
9
10 System.out.println("╔══════════════════════════════════════════╗");
11 System.out.println("║ ORDER VALIDATION SYSTEM ║");
12 System.out.println("╚══════════════════════════════════════════╝\n");
13
14 // Payment gateway returns a NEW String object — not from pool
15 String gatewayResponse = new String("SUCCESS");
16 String expected = "SUCCESS";
17
18 System.out.println("=== Payment Status Validation ===");
19 System.out.println("Response : " + gatewayResponse);
20 System.out.println("Expected : " + expected);
21 System.out.println("Wrong (==) : " + (gatewayResponse == expected)); // false — bug!
22 System.out.println("Correct (equals): "
23 + OrderValidator.validatePaymentStatus(gatewayResponse, expected)); // true
24
25 System.out.println();
26
27 // Order equality check
28 Order order1 = new Order("ORD-2024-001", "CUST-501", 1299.50);
29 Order order2 = new Order("ORD-2024-001", "CUST-501", 1299.50); // same order ID
30 Order order3 = new Order("ORD-2024-002", "CUST-502", 499.00); // different ID
31
32 System.out.println("=== Order Equality ===");
33 System.out.println("order1 == order2 : " + (order1 == order2)); // false
34 System.out.println("order1.equals(order2) : " + order1.equals(order2)); // true
35 System.out.println("isSameOrder(order1, order2) : "
36 + OrderValidator.isSameOrder(order1, order2)); // true
37 System.out.println("isSameOrder(order1, order3) : "
38 + OrderValidator.isSameOrder(order1, order3)); // false
39
40 System.out.println();
41
42 // Integer comparison in order counts
43 Map<String, Integer> orderCounts = new HashMap<>();
44 orderCounts.put("CUST-501", 200); // above Integer cache range
45 orderCounts.put("CUST-502", 50); // within Integer cache range
46
47 Integer count1 = orderCounts.get("CUST-501");
48 Integer count2 = 200;
49
50 System.out.println("=== Integer Count Comparison ===");
51 System.out.println("count1 value : " + count1);
52 System.out.println("count1 == 200 : " + (count1 == count2)); // false — above 127
53 System.out.println("count1.equals(count2): " + count1.equals(count2)); // true
54 System.out.println("count1 >= 100 : " + OrderValidator.meetsMinimumCount(count1, 100)); // true
55
56 System.out.println();
57
58 // Full validation pipeline
59 System.out.println("=== Full Pipeline ===");
60 Order[] orders = {order1, order2, order3};
61 String[] statuses = {new String("SUCCESS"), new String("FAILED"), new String("SUCCESS")};
62
63 for (int i = 0; i < orders.length; i++) {
64 boolean valid = OrderValidator.validatePaymentStatus(statuses[i], "SUCCESS");
65 System.out.printf(" %s | Status: %-8s | Payment: %s%n",
66 orders[i].getOrderId(),
67 statuses[i],
68 valid ? "VALIDATED" : "REJECTED");
69 }
70 }
71}Output:
╔══════════════════════════════════════════╗
║ ORDER VALIDATION SYSTEM ║
╚══════════════════════════════════════════╝
=== Payment Status Validation ===
Response : SUCCESS
Expected : SUCCESS
Wrong (==) : false
Correct (equals): true
=== Order Equality ===
order1 == order2 : false
order1.equals(order2) : true
isSameOrder(order1, order2) : true
isSameOrder(order1, order3) : false
=== Integer Count Comparison ===
count1 value : 200
count1 == 200 : false
count1.equals(count2): true
count1 >= 100 : true
=== Full Pipeline ===
ORD-2024-001 | Status: SUCCESS | Payment: VALIDATED
ORD-2024-001 | Status: FAILED | Payment: REJECTED
ORD-2024-002 | Status: SUCCESS | Payment: VALIDATED
Three distinct comparison failures that equals() solves correctly: API response Strings compared with == (would always fail), Order objects compared without equals() override (would always fail), and Integer values above 127 compared with == (would fail intermittently).
Best Practices
Use equals() for all object content comparison — always. This applies to String, Integer, Long, Double, and every custom class. The only exception is primitives (int, double, boolean, char), which have no equals() method and where == is the correct tool.
Put the known non-null constant on the left side of equals(). "SUCCESS".equals(gatewayResponse) is safe when gatewayResponse might be null. gatewayResponse.equals("SUCCESS") throws NullPointerException. This one habit eliminates an entire category of null-related crashes.
Use Objects.equals(a, b) when either side might be null. This utility handles all null combinations without requiring explicit null checks. It is the cleanest option when comparing two values that might both legitimately be null — like optional fields in a database record.
Never use == to compare Integer, Long, or other wrapper objects. The cache range of -128 to 127 makes == unreliable — it returns true for small values and false for larger ones. Always use .equals() or unbox to a primitive first.
Common Mistakes
Mistake 1 — Using == for String Values From Outside the Literal Pool
1// Common in login systems, API validation, configuration checks
2String statusFromDB = getFromDatabase("order_status"); // new heap String
3String expected = "PLACED";
4
5if (statusFromDB == expected) { // false — heap vs literal
6 processOrder(); // never executes — silent bug
7}
8
9if (expected.equals(statusFromDB)) { // true — correct content comparison
10 processOrder(); // executes correctly
11}Mistake 2 — Relying on Integer == for Values That Might Exceed 127
1Integer attempts = getLoginAttempts(); // from session — might be Integer(200)
2Integer threshold = 200;
3
4if (attempts == threshold) { // false when value > 127 — intermittent bug
5 lockAccount(); // silently fails for high-value integers
6}
7
8if (attempts.equals(threshold)) { // true — reliable for all values
9 lockAccount();
10}Mistake 3 — Calling equals() on a Possibly Null Variable
1String role = getUserRole(); // might return null
2
3if (role.equals("admin")) { // NullPointerException if role is null
4 grantAccess();
5}
6
7// Safe alternatives:
8if ("admin".equals(role)) { // constant on left — null-safe
9 grantAccess();
10}
11
12// Or:
13if (Objects.equals(role, "admin")) { // null-safe both sides
14 grantAccess();
15}Mistake 4 — Thinking equals() Without Override Compares Content
1class Ticket {
2 String id;
3 Ticket(String id) { this.id = id; }
4 // No equals() override
5}
6
7Ticket t1 = new Ticket("TKT-001");
8Ticket t2 = new Ticket("TKT-001");
9
10System.out.println(t1.equals(t2)); // false — Object.equals() is the same as ==
11// Without override, equals() checks reference — not id field
12
13// Fix: override equals() in Ticket class using the id fieldInterview Questions
Q1. What is the difference between == and equals() in Java?
== compares what is stored in the variable. For primitives, that is the value directly — so 5 == 5 is true. For objects, that is the memory address — so two different objects with the same content return false. equals() is a method that compares logical content. The default implementation in Object uses ==, so it also compares references. When a class overrides equals() — as String, Integer, and well-designed custom classes do — it compares meaningful fields instead. Use == for primitives and null checks. Use equals() for all object content comparison.
Q2. Why does Integer a = 127; Integer b = 127; a == b return true, but the same with 128 returns false?
Java caches Integer objects for values from -128 to 127 in a special pool (similar to the String pool). When you autobox a value in this range, Java reuses the same cached object rather than creating a new one. Both a and b get the same cached object, so == returns true. For values outside this range, Java creates a new Integer object on the heap each time, so a and b point to different objects and == returns false. This is why equals() must always be used for Integer comparison — its result is consistent regardless of value.
Q3. Why does equals() without an override behave the same as ==?
equals() is defined in java.lang.Object, the root of all Java classes. The default implementation simply returns this == obj — reference comparison. Every class inherits this default unless it explicitly overrides equals(). String, Integer, ArrayList, and other standard library classes override it to compare content. Custom classes must also override it if content equality is needed. Without an override, two different objects with identical fields return false from equals().
Q4. When should you use == instead of equals() for objects?
Use == for exactly two scenarios with objects: checking whether a reference is null — if (obj == null) — which is safe and idiomatic; and intentionally checking whether two variables point to the exact same object in memory — used in specific caching or singleton patterns. For all content comparison, use equals(). The rule: == checks identity, equals() checks equivalence.
Q5. What is Objects.equals() and why is it better than equals() in some cases?
Objects.equals(a, b) is a static utility method from java.util.Objects. It handles null on both sides without any explicit null check: if both are null, it returns true; if one is null, it returns false; if neither is null, it calls a.equals(b). This is safer than a.equals(b) when a might be null, and safer than "constant".equals(b) when both variables might be null. Use Objects.equals() when either argument could legitimately be null — like optional fields, database values that might not be set, or method return values that might return null.
Q6. Does equals() handle null arguments safely?
The Object.equals() contract requires that x.equals(null) must return false for any non-null x. Well-implemented classes — including String — satisfy this contract. However, calling equals() on a null reference — nullVariable.equals("something") — always throws NullPointerException. The method must be called on a non-null object. The constant-on-left pattern "something".equals(variable) handles this correctly regardless of whether variable is null.
FAQs
Can == ever be correct for String comparison?
Only in two specific situations: when you intentionally want to check whether two String variables are the same interned object — which is almost never a business requirement — or when comparing a String reference to null using str == null. For any check involving string content, equals() is the correct and only reliable choice.
Why does null == null return true with == but Objects.equals(null, null) also returns true?
== checks memory addresses. Both null references point to nothing — address 0 — so == returns true. Objects.equals(a, b) explicitly handles the both-null case: if both arguments are null, it returns true without calling any method. This means Objects.equals(null, null) is true and Objects.equals(null, "hello") is false — both safely.
Is it ever correct to compare double values with ==?
For exact equality of literal constants — price == 0.0 — == is technically safe. But comparing results of arithmetic — like 0.1 + 0.2 == 0.3 — is unreliable because floating-point arithmetic introduces tiny precision errors. The result of 0.1 + 0.2 is 0.30000000000000004, not exactly 0.3. Use Math.abs(a - b) < epsilon where epsilon is a small tolerance value like 0.0001.
Do records in Java automatically provide correct equals() behaviour?
Yes. Java 16+ records automatically generate equals(), hashCode(), and toString(). Two record instances are equal when all their component values are equal — the generated equals() compares all declared fields. You can override the generated equals() in the record body if you need different equality semantics.
What does the equals() contract require?
The Java specification requires equals() to be: reflexive — x.equals(x) is always true; symmetric — if x.equals(y) then y.equals(x); transitive — if x.equals(y) and y.equals(z) then x.equals(z); consistent — repeated calls return the same result when no fields change; and null-safe — x.equals(null) must return false, never throw. Violating any of these causes unpredictable behaviour in collections like HashSet and HashMap.
Summary
== stores what the variable holds. For primitives, that is a value — comparison is direct and correct. For objects, that is a memory address — comparison checks identity, not content. equals() is a method that compares logical content when properly overridden, which is what the vast majority of comparisons in application code actually need.
The two rules that prevent almost every comparison bug: use equals() for all object content comparison instead of ==; and put the known non-null value on the left side of equals(), or use Objects.equals() when either side might be null. The Integer cache and String pool are interview topics that become clear once you understand why == is unreliable for objects — the cache is an optimisation that occasionally makes == appear to work, which is more confusing than helpful.
What to Read Next
Learn where Java stores string values in memory.