skip to content

What is the difference between assertEquals and assertSame in JUnit 5, and when would you use each?

level: middleimportance: should knowfreq 55%

answer

  1. assertEquals -> .equals() (value)
  2. assertSame -> == (identity)
  3. assertNotEquals / assertNotSame are the negations
  4. assertSame for cache/intern/singleton/return-this
  5. Integer cache -128..127 = accidental assertSame pass

basics

~20 s

assertEquals checks that two objects are equal by value (using .equals()). assertSame checks they are the exact same object in memory (using ==). Use assertSame only when identity matters, like verifying a cache returns the same instance.

solid answer

~50 s

assertEquals tests logical equality: it compares with .equals(), so two distinct objects whose fields match (e.g. two equal Strings or two equal BigDecimals) pass. assertSame tests reference identity with ==, so it passes only when both arguments are the very same object on the heap. There are also assertNotEquals and assertNotSame for the negations. You reach for assertSame in narrow cases where identity is the contract: a cache or interning pool that must hand back the same instance, an enum constant, a singleton, or verifying a method returns 'this' for fluent chaining. For ordinary value checks — the vast majority of tests — use assertEquals. A common bug is using assertSame on values that happen to be identical because of JVM caching (small Integer or interned String literals), which passes by accident and then fails when the value leaves the cached range.

code

java · 20 lines
java
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

class SameVsEqualsTest {
    @Test
    void valueEqualityUsesAssertEquals() {
        String a = new String("hi");
        String b = new String("hi");
        assertEquals(a, b);   // equal value -> passes
        assertNotSame(a, b);  // different objects -> passes
    }

    @Test
    void identityUsesAssertSame() {
        var pool = new java.util.HashMap<Integer, Object>();
        Object cached = pool.computeIfAbsent(1, k -> new Object());
        Object again  = pool.computeIfAbsent(1, k -> new Object());
        assertSame(cached, again); // cache must return the same instance
    }
}

go deeper

for a junior

Know that assertEquals compares values and assertSame compares whether it's literally the same object.

for a middle

Map assertEquals to .equals() and assertSame to ==, name assertNotEquals/assertNotSame, and give a concrete identity use case like a cache returning the same instance.

for a senior

Discuss the accidental-pass hazard from Integer/String caching and choose assertSame deliberately only when identity is the contract under test.

for a principal

Advise teams to treat identity assertions as a code smell unless the API contract is explicitly about identity (interning, pooling, singletons), and to prefer value equality plus equals/hashCode correctness elsewhere.

## Two kinds of 'the same' In Java there are two distinct notions of two things being 'equal': 1. **Reference identity** — the two variables point at the *exact same object* in memory. Tested with the `==` operator. 2. **Logical (value) equality** — the two objects may be *different objects* but represent the *same value*, as decided by the object's `.equals()` method (e.g. `new String("a").equals("a")` is true even though they are two separate objects). JUnit 5 gives you one assertion for each. ## assertEquals — value equality `assertEquals(expected, actual)` passes when `expected.equals(actual)` is true (for primitives it compares the values directly). This is what you want almost always: you care that a method returned the *right value*, not which specific object instance carries it. ```java assertEquals("HELLO", "hello".toUpperCase()); // two different String objects, equal value -> passes ``` ## assertSame — reference identity `assertSame(expected, actual)` passes only when `expected == actual`, i.e. both refer to the literally identical object. ```java MyEnum a = MyEnum.X; assertSame(MyEnum.X, a); // same constant instance -> passes assertEquals(new Point(1,2), new Point(1,2)); // different objects, equal value -> assertSame would FAIL ``` ## The negations JUnit also provides `assertNotEquals(unexpected, actual)` (passes when they are *not* value-equal) and `assertNotSame(unexpected, actual)` (passes when they are *not* the same reference). Use these to assert that, for example, a defensive copy produced a *different* object: `assertNotSame(original, copy)` together with `assertEquals(original, copy)`. ## When identity actually matters Reach for `assertSame` only when the *contract under test is about identity*: - a **cache** or **object pool** must return the same cached instance on repeated lookups; - an **intern**/canonicalization map must collapse equal inputs to one shared instance; - a **singleton** must always yield the same instance; - a fluent/builder method must `return this`; - an **enum** constant comparison. ## The classic accidental-pass bug The JVM caches some immutable values. `Integer.valueOf(n)` returns a cached instance for `-128..127`, and string literals are interned. So: ```java assertSame(127, Integer.valueOf(127)); // passes by accident (cached) assertSame(128, Integer.valueOf(128)); // FAILS — outside the cache range ``` Using `assertSame` where you meant value equality can pass for small/cached values and then break for larger ones, producing a confusing, data-dependent failure. Default to `assertEquals` unless identity is genuinely the thing you are testing.

  • Why might assertSame(127, Integer.valueOf(127)) pass but assertSame(128, ...) fail?
    The JVM caches boxed Integers in the range -128..127, so valueOf(127) returns the same shared instance as the literal. Above 127 it creates a new object, so == (and assertSame) fails. It's a JVM caching artifact, not a value comparison.
  • How would you assert that a method made a defensive copy?
    Combine assertEquals(original, copy) to confirm the value matches and assertNotSame(original, copy) to confirm it's a different object instance.

saying these in an interview costs you the question

  • Saying assertSame just calls .equals() like assertEquals
  • Defaulting to assertSame for ordinary value checks
  • Not knowing that small Integer/interned String values make assertSame pass by accident

context