The Context

In Java, we have two methods for equality comparison of values: equals() and the operator ==.

So what’s the difference between using == and equals() in Java? Turns out there’s a lot.

Example: java.net.URL equality comparison

java.net.URL is a good reminder that equals() is not automatically “compare every field” in Java. The JDK defines two URL instances as equal when they refer to the same resource, so the comparison is about the class’s meaning of equality, not object identity.

That already makes it different from ==. If we create two separate URL objects, == still asks whether both variables store the same reference value, while equals() asks whether both URLs should count as the same location according to the API contract. The surprising part is that host comparison may require name resolution 1 .

URL a = new URL("http://example.com/docs");
URL b = new URL("http://EXAMPLE.COM/docs");

a == b;       // false: distinct objects
a.equals(b);  // can be true: equality follows URL semantics

This is a compact example of the whole topic: == compares the stored value itself, while equals() compares whatever the class defines as meaningful equality.

The Mechanics

To understand how primitive values and objects are asserted for equality in Java, I think we must first roughly understand how they are managed memory-wise.

For context, Java is a statically and strongly typed language. Static here means that every variable and expression has a type that is known at compile time, and strongly here means that the type of the value does not change. See Chapter 4 of the Java Language Specification for more detail on the type system.

Java Type System

Java is object-oriented, but not everything is an object since there are primitive values. In other words, there are two kinds of types in Java: primitive types and reference types.

It is clear that a primitive value such as an integer 5 is simply represented as ...000101 in binary format in memory.

However, in Java, according to JVM Spec section 2.3, the specification only guarantees the semantics are 32-bit for int values. JVM implementations such as HotSpot and OpenJ9 still have freedom in how they map the abstract machine to physical hardware.

Meanwhile, reference type values such as classes, arrays, interfaces, and String are really just reference values that point the JVM toward the actual object in heap space.

Most simply, reference type values are values that the JVM can use to find the actual object during runtime.

The first distinction is conceptual: primitives are represented by their bits directly, while reference variables store a value the JVM can use to locate an object.

That is why == feels simple for primitives and more subtle for objects. The variable does not carry the whole object; it carries enough information for the runtime to resolve one.

Primitive values are stored directly; reference values let the JVM locate an object.
Primitive versus reference illustration.

Primitive values are stored directly; reference values let the JVM locate an object.

Less simply, a reference is a value that identifies an object; how it is represented, whether as a pointer, handle, compressed pointer, or something else, is JVM-dependent.

In the HotSpot implementation, a reference is a bit pattern representing a virtual memory address, either a pointer or a compressed offset decoded into a pointer, that indicates the starting byte of the JVM object header on the heap.

A useful mental model is that the reference value identifies where the object begins, not what the object contains semantically. That keeps the distinction between object identity and object state visible.

The exact encoding remains JVM-dependent, so this is an implementation-oriented picture rather than a universal law of the language.

One implementation detail: a reference can be mapped to the object header in heap memory.
Reference representation illustration.

One implementation detail: a reference can be mapped to the object header in heap memory.

Pass-by-value

Now that we’ve seen something about how Java manages its objects, we can look at why Java is pass-by-value and not pass-by-reference.

  • The assumption: when passing an object to a method, we are passing the object itself, or rather the direct link to the original one.

This is the trap: because a method can mutate the object through the received reference, it is easy to describe that behavior as pass-by-reference.

But mutation through a copied reference value is still different from passing the caller’s variable itself. That distinction is exactly where many equality explanations get blurred.

The common but incorrect intuition is that Java passes the original object into the method.
Incorrect pass-by-reference intuition.

The common but incorrect intuition is that Java passes the original object into the method.

  • The reality: Java copies the bits inside the original variable. Since p holds a reference value in this example, that value is copied into the new method parameter.

Once framed this way, the phrase “Java is pass-by-value” becomes much less mysterious. The copied value can still lead both variables to the same heap object, but the variables remain separate bindings.

That is also why == on references compares whether the stored reference values identify the same object, not whether two objects merely look alike.

Method calls copy the stored value; for objects, that copied value is the reference.
What Java actually copies.

Method calls copy the stored value; for objects, that copied value is the reference.

So in short, Java is pass-by-value, and the values passed are reference values for reference types.

Conclusion on Equality Comparison

Now that we have cleared the concepts around references in Java, we can finally produce a clearer answer on how == and .equals() differ:

OperatorPrimitive typeReference type
==Compares value-by-valueCompares the reference value, which could be the heap address
.equals()Primitive types do not have methods

Depends on the specific implementation. For example, String.equals() compares underlying content. A more complex equals() implementation can be seen in HashMap.

References

  1. Java Language Specification
  2. Java Platform, Standard Edition & Java Development Kit Version 25 API Specification
  3. Java SE Spec, Chapter 4