Back to Operators & Expressions
Q1Question 1 of 9

What is the difference between == and .equals()?

Key Concepts
  • == compares values for primitives (int, double, boolean) and references (memory address) for objects
  • .equals() compares logical content – overridden in String, Integer, BigDecimal to compare values
  • Two different String objects with same text: == is false, .equals() is true
  • String literals are interned (shared), so == accidentally works – but new String() breaks it
  • BigDecimal: use compareTo()==0 for value, not .equals() which is scale-sensitive
  • Always override both equals() and hashCode() together – HashMap depends on it
  • Use Objects.equals(a, b) when either side might be null

Definition

== compares identity (same object in memory) for references and raw value for primitives. .equals() compares logical content. Always use .equals() for objects.

The difference is identity versus content:

  • == on primitives compares values directly. 5 == 5 is true. This is correct and the only option.
  • == on objects compares references (memory addresses). Two different String objects with the same text are == false because they are different objects.
  • .equals() on objects compares content. String.equals() compares characters. Integer.equals() compares int values. Classes must override it to define what "equal content" means.
  • Default .equals() in Object uses == (reference comparison). If you do not override it, .equals() is the same as ==.

Two bank passbooks for Rajesh's account SAV2001. Using == asks: "Is this the exact same physical booklet?" Using .equals() asks: "Do both passbooks show the same account number and balance?" Two separate printouts are different booklets (== false) but describe the same account (.equals() true).

Understanding the Concepts

How does == work on primitives?

For primitives, == compares the actual values. This is correct and the only way to compare them.

JAVA
int a = 42, b = 42;
System.out.println(a == b);  // true (values are equal)

double x = 3.14, y = 3.14;
System.out.println(x == y);  // true

boolean p = true, q = true;
System.out.println(p == q);  // true

Primitives do not have an .equals() method. You must use ==.

How does == work on objects?

For objects, == compares references (memory addresses), not values.

JAVA
String a = new String("SAV2001");
String b = new String("SAV2001");

System.out.println(a == b);       // false (different objects in memory)
System.out.println(a.equals(b));  // true (same characters)

Even though both strings contain "SAV2001", they are two separate objects on the heap. == checks if both variables point to the exact same object, which they do not.

Why do String literals work with ==?

String literals are automatically interned into a shared pool. Two identical literals return the same pooled object.

JAVA
String a = "SAV2001";     // interned (pool)
String b = "SAV2001";     // same interned object
System.out.println(a == b);       // true (same object from pool)
System.out.println(a.equals(b));  // true (same characters)

String c = new String("SAV2001"); // new object on heap
System.out.println(a == c);       // false (different object!)
System.out.println(a.equals(c));  // true (same characters)

System.out.println(a == c.intern()); // true (intern returns the pooled object)

Never rely on this. Strings from databases, user input, and network calls are not interned. Always use .equals().

How does .equals() work for BigDecimal?

BigDecimal has a trap: .equals() is scale-sensitive. Two amounts that represent the same value but have different scales are not .equals().

JAVA
BigDecimal a = new BigDecimal("50000");     // scale 0
BigDecimal b = new BigDecimal("50000.00");  // scale 2

System.out.println(a.equals(b));         // false (different scale!)
System.out.println(a.compareTo(b) == 0); // true (same numeric value)

For money comparison, always use compareTo() == 0, not .equals().

How do you override equals() correctly?

When you create a domain class like Account, you must override .equals() to define what "equal" means for your objects.

JAVA
public class Account {
    private final String accountId;
    private final String holderName;

    public Account(String accountId, String holderName) {
        this.accountId = accountId;
        this.holderName = holderName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;                    // same reference
        if (!(o instanceof Account other)) return false; // type check (Java 16+)
        return accountId.equals(other.accountId);       // business key
    }

    @Override
    public int hashCode() {
        return accountId.hashCode();  // consistent with equals
    }
}

The equals contract (from Object javadoc):

  • Reflexive: a.equals(a) is always true.
  • Symmetric: if a.equals(b) then b.equals(a).
  • Transitive: if a.equals(b) and b.equals(c) then a.equals(c).
  • Consistent: same result every time if objects are unchanged.
  • Null-safe: a.equals(null) is always false.

Why must you override hashCode() together with equals()?

HashMap and HashSet use hashCode() to find the bucket, then .equals() to match the key. If two objects are .equals() but have different hash codes, HashMap cannot find them.

JAVA
// WITHOUT overriding hashCode: HashMap breaks
Account a1 = new Account("SAV2001", "Rajesh");
Account a2 = new Account("SAV2001", "Rajesh");

Map<Account, BigDecimal> balances = new HashMap<>();
balances.put(a1, new BigDecimal("1000000"));

// a1.equals(a2) is true, but without hashCode override,
// they may land in different buckets
System.out.println(balances.get(a2));  // null! (cannot find it)

// WITH hashCode override: works correctly
System.out.println(balances.get(a2));  // 1000000

Rule: if you override equals(), you must override hashCode(). If two objects are equal, they must have the same hash code.

What about Objects.equals() for null safety?

a.equals(b) throws NullPointerException if a is null. Objects.equals() handles null on both sides.

JAVA
import java.util.Objects;

String a = null;
String b = "SAV2001";

// a.equals(b);              // NPE! a is null
System.out.println(Objects.equals(a, b));    // false (no NPE)
System.out.println(Objects.equals(null, null)); // true
System.out.println(Objects.equals(a, a));    // true

Use Objects.equals() when either side might be null.

What about enums?

For enums, == is correct and preferred. Each enum constant is a singleton, so there is only one instance of each value.

JAVA
enum AccountType { SAVINGS, CURRENT, LOAN }

AccountType a = AccountType.SAVINGS;
AccountType b = AccountType.SAVINGS;

System.out.println(a == b);       // true (same singleton)
System.out.println(a.equals(b));  // true (also works)
// == is preferred for enums: it is null-safe and faster
System.out.println(a == null);    // false (no NPE)
// a.equals(null) also works but == is cleaner

What about arrays?

== on arrays compares references (are they the same array object?). To compare contents, use Arrays.equals().

JAVA
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};

System.out.println(a == b);               // false (different objects)
System.out.println(a.equals(b));          // false (Object.equals = reference)
System.out.println(Arrays.equals(a, b));  // true (compares elements)

// For nested arrays (2D), use Arrays.deepEquals()
int[][] x = {{1, 2}, {3, 4}};
int[][] y = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepEquals(x, y));  // true

What about Java records?

Records (Java 16+) auto-generate equals() and hashCode() based on all components.

JAVA
public record AccountSummary(String accountId, BigDecimal balance) {}

var a = new AccountSummary("SAV2001", new BigDecimal("1000000"));
var b = new AccountSummary("SAV2001", new BigDecimal("1000000"));

System.out.println(a.equals(b));  // true (auto-generated, compares all components)
System.out.println(a == b);       // false (different objects)

Records eliminate the need to manually write equals/hashCode for simple data carriers.

What are the complete rules?

| Comparison | Use | Why |
|---|---|---|
| Primitives (int, double, boolean) | == | Only option, compares values |
| Null check | == | x == null is idiomatic |
| Enums | == | Singletons, null-safe, fast |
| String content | .equals() | Compares characters |
| Integer/Long/wrapper content | .equals() | Compares wrapped value |
| BigDecimal value | compareTo() == 0 | Scale-independent |
| Custom objects | .equals() (override) | Define business equality |
| Arrays | Arrays.equals() | Element-by-element |
| Nullable references | Objects.equals() | Null-safe |

What are common mistakes?

  • Using == for String comparison. Works for literals, fails for strings from DB/network/user input.
  • Using .equals() for BigDecimal money comparison. Scale-sensitive: 50000 is not .equals() to 50000.00. Use compareTo().
  • Overriding equals() without hashCode(). HashMap and HashSet break silently.
  • Calling .equals() on a null reference. nullRef.equals(x) throws NPE. Use Objects.equals() or put the known non-null value on the left: "SAV2001".equals(maybeNull).
  • Using == for wrapper comparison. Works for Integer -128 to 127 (cached), fails for larger values.

What should a senior engineer know?

  • Lombok @EqualsAndHashCode generates equals/hashCode. Use @EqualsAndHashCode(of = "accountId") to base equality on the business key only.
  • JPA entities: use business key, not @Id. Generated IDs are null before persist. Base equals on a natural key like accountId.
  • Immutable objects simplify equals. If fields never change, equals/hashCode are stable. Records enforce this.
  • instanceof pattern matching (Java 16+) simplifies equals: if (!(o instanceof Account other)) return false; combines the type check and cast in one line.
  • String.intern() is rarely needed. The pool saves memory for repeated literals, but manually interning strings from external sources can leak memory if the strings are unique.

Visual Overview

DIAGRAM
  == operator:
    primitives → compares VALUES           → correct
    objects    → compares REFERENCES        → usually wrong for value check

  .equals() method:
    Object default → same as == (reference) → useless
    String override → compares characters   → correct
    Integer override → compares int value   → correct
    BigDecimal override → compares value AND scale → tricky

  Decision:
    primitive?     → ==
    null check?    → ==
    enum?          → == (preferred)
    String?        → .equals()
    BigDecimal?    → compareTo() == 0
    custom object? → override .equals() + .hashCode()
    array?         → Arrays.equals()
    nullable?      → Objects.equals()

Key Takeaway

The difference between == and .equals() is identity versus content. For primitives, == compares the actual values and is the only option. For objects, == compares memory addresses – it asks "are these the same object?" while .equals() asks "do these objects represent the same value?"

Two separate String objects containing "SAV2001" are == false (different objects) but .equals() true (same characters). String literals accidentally work with == because they are interned into a shared pool, but strings from databases, network calls, and user input are not interned. Always use .equals().

For BigDecimal, even .equals() is not enough for money comparison because it is scale-sensitive. Use compareTo() == 0 instead. For enums, == is correct and preferred because each constant is a singleton. For arrays, use Arrays.equals() because == on arrays compares references. For null-safe comparison, use Objects.equals().

When you create domain classes, override both equals() and hashCode() together based on the business key. HashMap and HashSet depend on this contract. Records auto-generate both, which is ideal for DTOs and value objects.

One sentence to remember: == compares identity for objects and value for primitives; .equals() compares content – always use .equals() for objects, compareTo for BigDecimal, and override both equals and hashCode in your domain classes.

---

Share
Use ← → arrow keys to navigate