Java Tutorial
🔍

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.

// File: PrimitiveEquality.java public class PrimitiveEquality { public static void main(String[] args) { // int comparison int a = 100; int b = 100; int c = 200; System.out.println("=== int ==="); System.out.println("a == b : " + (a == b)); // true — both store 100 System.out.println("a == c : " + (a == c)); // false — 100 != 200 // double comparison double price1 = 99.99; double price2 = 99.99; System.out.println("\n=== double ==="); System.out.println("price1 == price2 : " + (price1 == price2)); // true // boolean comparison boolean isActive = true; boolean isPending = false; System.out.println("\n=== boolean ==="); System.out.println("isActive == true : " + (isActive == true)); // true System.out.println("isPending == isActive: " + (isPending == isActive)); // false // char comparison — compares Unicode values char grade1 = 'A'; char grade2 = 'A'; char grade3 = 'B'; System.out.println("\n=== char ==="); System.out.println("grade1 == grade2 : " + (grade1 == grade2)); // true — same Unicode System.out.println("grade1 == grade3 : " + (grade1 == grade3)); // false // Floating-point precision warning double x = 0.1 + 0.2; double y = 0.3; System.out.println("\n=== Floating-point precision ==="); System.out.println("0.1 + 0.2 == 0.3 : " + (x == y)); // false — precision issue! System.out.println("0.1 + 0.2 = " + x); // 0.30000000000000004 System.out.println("Use Math.abs for double comparison: " + (Math.abs(x - y) < 0.0001)); // true — safe comparison } }
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.

// File: ObjectEquality.java public class ObjectEquality { static class Product { String name; double price; Product(String name, double price) { this.name = name; this.price = price; } } public static void main(String[] args) { Product p1 = new Product("Laptop", 45000.0); Product p2 = new Product("Laptop", 45000.0); // identical data Product p3 = p1; // same reference System.out.println("=== Object Reference Check ==="); System.out.println("p1 == p2 : " + (p1 == p2)); // false — two different objects System.out.println("p1 == p3 : " + (p1 == p3)); // true — same object System.out.println("p2 == p3 : " + (p2 == p3)); // false System.out.println(); // Identity hash code confirms different objects System.out.println("p1 address: " + System.identityHashCode(p1)); System.out.println("p2 address: " + System.identityHashCode(p2)); // different System.out.println("p3 address: " + System.identityHashCode(p3)); // same as p1 System.out.println(); // What == actually means for objects System.out.println("=== What == means for objects ==="); System.out.println("p1 == p2 means: do p1 and p2 point to the same object?"); System.out.println("Answer: " + (p1 == p2)); // false — they are different objects System.out.println(); System.out.println("p1.name.equals(p2.name): " + p1.name.equals(p2.name)); // true System.out.println("p1.price == p2.price : " + (p1.price == p2.price)); // true (primitive) } }
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.

// File: StringEquality.java public class StringEquality { public static void main(String[] args) { // Case 1 — String literals — stored in String Pool — same object reused String s1 = "hello"; String s2 = "hello"; System.out.println("=== String Literals (Pool) ==="); System.out.println("s1 == s2 : " + (s1 == s2)); // true — same pool object System.out.println("s1.equals(s2) : " + s1.equals(s2)); // true System.out.println(); // Case 2 — new String() — always creates a new heap object String s3 = new String("hello"); String s4 = new String("hello"); System.out.println("=== new String() (Heap) ==="); System.out.println("s3 == s4 : " + (s3 == s4)); // false — different objects System.out.println("s3.equals(s4) : " + s3.equals(s4)); // true — same content System.out.println("s1 == s3 : " + (s1 == s3)); // false — pool vs heap System.out.println(); // Case 3 — runtime-built strings — always new heap objects String base = "hel"; String concat = base + "lo"; // built at runtime — new heap object System.out.println("=== Runtime Concatenation (Heap) ==="); System.out.println("s1 == concat : " + (s1 == concat)); // false — concat is on heap System.out.println("s1.equals(concat): " + s1.equals(concat)); // true — same content System.out.println(); // The lesson — NEVER use == for string content comparison // Strings from database, user input, API — always heap objects System.out.println("=== The Real-World Problem ==="); String fromDB = fetchFromDatabase(); // always a new heap object String expected = "active"; System.out.println("fromDB == expected : " + (fromDB == expected)); // false — bug! System.out.println("expected.equals(fromDB): " + expected.equals(fromDB)); // true — correct } // Simulates reading a String from a database or API response static String fetchFromDatabase() { return new String("active"); // always a new heap object in reality } }
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.

// File: WrapperEquality.java public class WrapperEquality { public static void main(String[] args) { // Integer cache — -128 to 127 are cached Integer a = 100; Integer b = 100; Integer c = 200; Integer d = 200; System.out.println("=== Integer Cache Trap ==="); System.out.println("a == b (value 100): " + (a == b)); // true — cached range! System.out.println("c == d (value 200): " + (c == d)); // false — outside cache System.out.println(); System.out.println("a.equals(b) : " + a.equals(b)); // true — always reliable System.out.println("c.equals(d) : " + c.equals(d)); // true — always reliable System.out.println(); // Why the cache exists — performance optimisation // Autoboxing frequently used small integers reuses the same objects System.out.println("=== Why This Happens ==="); System.out.println("Integer.valueOf(100) == Integer.valueOf(100): " + (Integer.valueOf(100) == Integer.valueOf(100))); // true — cached System.out.println("Integer.valueOf(200) == Integer.valueOf(200): " + (Integer.valueOf(200) == Integer.valueOf(200))); // false — not cached System.out.println(); // Same trap with Long Long x = 50L; Long y = 50L; Long p = 200L; Long q = 200L; System.out.println("=== Long Cache ==="); System.out.println("50L == 50L : " + (x == y)); // true — in cache range System.out.println("200L == 200L : " + (p == q)); // false — outside cache System.out.println(); // Safe comparison — always use equals() for wrapper types System.out.println("=== Always Use equals() for Wrappers ==="); Integer score1 = 500; Integer score2 = 500; System.out.println("score1 == score2 : " + (score1 == score2)); // false — unreliable System.out.println("score1.equals(score2) : " + score1.equals(score2)); // true — reliable // Unboxing — comparing wrapper to primitive uses == safely int primitive = 500; System.out.println("score1 == primitive : " + (score1 == primitive)); // true — auto-unboxed } }
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.

// File: EqualsOverrideDemo.java import java.util.Objects; public class EqualsOverrideDemo { // Without override — uses Object.equals() which is the same as == static class ProductWithout { String sku; ProductWithout(String sku) { this.sku = sku; } } // With override — compares content correctly static class Product { private final String sku; private final String name; private final double price; Product(String sku, String name, double price) { this.sku = sku; this.name = name; this.price = price; } @Override public boolean equals(Object obj) { if (this == obj) return true; // Step 1: same reference if (obj == null) return false; // Step 2: null check if (!(obj instanceof Product other)) return false; // Step 3: type check return Objects.equals(sku, other.sku); // Step 4: field comparison // Two Products are "equal" if they have the same SKU } @Override public int hashCode() { return Objects.hash(sku); // must match equals — same field } @Override public String toString() { return "Product{" + sku + ", " + name + ", Rs." + price + "}"; } } public static void main(String[] args) { // Without override — equals() behaves like == ProductWithout pw1 = new ProductWithout("SKU-001"); ProductWithout pw2 = new ProductWithout("SKU-001"); System.out.println("=== Without Override ==="); System.out.println("pw1 == pw2 : " + (pw1 == pw2)); // false System.out.println("pw1.equals(pw2) : " + pw1.equals(pw2)); // false — same as == System.out.println(); // With override — equals() compares SKU content Product p1 = new Product("SKU-001", "Laptop", 45000.0); Product p2 = new Product("SKU-001", "Laptop", 45000.0); // same SKU Product p3 = new Product("SKU-002", "Monitor", 18000.0); // different SKU System.out.println("=== With Override ==="); System.out.println("p1 == p2 : " + (p1 == p2)); // false — different objects System.out.println("p1.equals(p2) : " + p1.equals(p2)); // true — same SKU System.out.println("p1.equals(p3) : " + p1.equals(p3)); // false — different SKU System.out.println("p1.equals(null): " + p1.equals(null)); // false — null safe System.out.println(); // HashSet deduplication depends on correct equals() + hashCode() java.util.Set<Product> catalogue = new java.util.HashSet<>(); catalogue.add(p1); catalogue.add(p2); // same SKU as p1 — should not be added catalogue.add(p3); System.out.println("Unique products in catalogue: " + catalogue.size()); // 2 } }
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()
TypeLanguage operatorMethod from java.lang.Object
For primitivesCompares values directlyNot applicable — primitives have no methods
For objectsCompares memory addresses (references)Compares logical content — depends on override
Default object behaviourReference equalitySame as == if not overridden in the class
Can be overriddenNo — operator is fixedYes — every class can define its own logic
Null safetynull == null is trueThrows NullPointerException if called on null
String literalsMay return true (pool reuse)Always returns true for equal content
Strings from DB / APIReturns false (heap objects)Returns true for equal content
Integer values 127 or belowReturns true (cached)Always returns true for equal values
Integer values above 127Returns false (heap)Always returns true for equal values
Used in HashSet / HashMapNever — collections use equals()Always — alongside hashCode()
When to useReference identity, null checks, primitivesContent equality for objects
Reliable for contentNo — unpredictable for objectsYes — always reliable when correctly overridden

Null Handling — equals() vs == vs Objects.equals()

// File: NullHandlingDemo.java import java.util.Objects; public class NullHandlingDemo { public static void main(String[] args) { String a = "hello"; String b = null; String c = null; // == with null — always safe System.out.println("=== == with null ==="); System.out.println("b == null : " + (b == null)); // true System.out.println("a == null : " + (a == null)); // false System.out.println("b == c : " + (b == c)); // true — both null System.out.println(); // equals() with null — NPE if called on null reference System.out.println("=== equals() with null ==="); try { System.out.println(b.equals("hello")); // NPE — b is null } catch (NullPointerException e) { System.out.println("b.equals('hello') → NullPointerException!"); } // Safe pattern — constant on left System.out.println("'hello'.equals(b): " + "hello".equals(b)); // false — no NPE System.out.println(); // Objects.equals() — null-safe on BOTH sides System.out.println("=== Objects.equals() — null-safe both sides ==="); System.out.println("Objects.equals(a, 'hello'): " + Objects.equals(a, "hello")); // true System.out.println("Objects.equals(b, 'hello'): " + Objects.equals(b, "hello")); // false System.out.println("Objects.equals(b, c) : " + Objects.equals(b, c)); // true — both null System.out.println("Objects.equals(a, null) : " + Objects.equals(a, null)); // false System.out.println(); // When to use each System.out.println("=== Choosing the right tool ==="); System.out.println("Use == null : to check if a reference is null"); System.out.println("Use equals() : when you KNOW left side is non-null"); System.out.println("Use Objects.equals(): when EITHER side could be null"); } }
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

Basic Example

public class Main { public static void main(String[] args) { // Primitives — == compares values directly int a = 5; int b = 5; System.out.println("a == b : " + (a == b)); // true // Objects created with new — always different memory addresses String s1 = new String("cat"); String s2 = new String("cat"); System.out.println("s1 == s2 : " + (s1 == s2)); // false System.out.println("s1.equals(s2) : " + s1.equals(s2)); // true // String literals — reused from the pool — same address String s3 = "cat"; String s4 = "cat"; System.out.println("s3 == s4 : " + (s3 == s4)); // true } }

Output

a == b            : true
s1 == s2          : false
s1.equals(s2)     : true
s3 == s4          : true

How it works:

  1. ›a == b compares two int values directly, so it is true when both hold 5.
  2. ›s1 and s2 are created with new String(...), so each is a separate object at a different memory address — == returns false even though the text is identical.
  3. ›s1.equals(s2) compares the actual characters, not the address, so it correctly returns true.
  4. ›s3 and s4 are string literals, so both point to the same object in the String pool, making == return true for them.

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

// Common in login systems, API validation, configuration checks String statusFromDB = getFromDatabase("order_status"); // new heap String String expected = "PLACED"; if (statusFromDB == expected) { // false — heap vs literal processOrder(); // never executes — silent bug } if (expected.equals(statusFromDB)) { // true — correct content comparison processOrder(); // executes correctly }

Mistake 2 — Relying on Integer == for Values That Might Exceed 127

Integer attempts = getLoginAttempts(); // from session — might be Integer(200) Integer threshold = 200; if (attempts == threshold) { // false when value > 127 — intermittent bug lockAccount(); // silently fails for high-value integers } if (attempts.equals(threshold)) { // true — reliable for all values lockAccount(); }

Mistake 3 — Calling equals() on a Possibly Null Variable

String role = getUserRole(); // might return null if (role.equals("admin")) { // NullPointerException if role is null grantAccess(); } // Safe alternatives: if ("admin".equals(role)) { // constant on left — null-safe grantAccess(); } // Or: if (Objects.equals(role, "admin")) { // null-safe both sides grantAccess(); }

Mistake 4 — Thinking equals() Without Override Compares Content

class Ticket { String id; Ticket(String id) { this.id = id; } // No equals() override } Ticket t1 = new Ticket("TKT-001"); Ticket t2 = new Ticket("TKT-001"); System.out.println(t1.equals(t2)); // false — Object.equals() is the same as == // Without override, equals() checks reference — not id field // Fix: override equals() in Ticket class using the id field

Interview 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

Practice Programs

Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.

Browse all Java programs →