Look at this Java code. It looks very simple, but it consistently tricks even some experienced Java developers:
Integer a = 100;
Integer b = 100;
System.out.println(a == b);
Prints True.
Integer x = 200;
Integer y = 200;
System.out.println(x == y);
Prints False.
How is this possible? If 100 == 100 is true, why does Java think 200 == 200 is false? It feels like something is broken in the language.
In reality, this isn’t a bug at all. It is an intentional memory optimization feature built deep into the Java Virtual Machine (JVM).
The Problem with ==
To understand this, we first need to remember how the double equals (==) operator works when dealing with objects.
When you use == to compare two objects in Java (like Integer variables), Java does not compare their actual numerical values. Instead, it compares their memory addresses. It is simply asking: “Do these two variables point to the exact same spot in memory?”
Normally, when you create two objects, Java allocates two separate spaces in memory for them. Even if both objects hold the value 200, they live at two different addresses (let’s call them Address A and Address B).
Because Address A is not the same as Address B, x == y returns false. That makes perfect logical sense.
Makes sense then why does this happen? why 100 == 100 return true? as per the above logic both the Integer objects should be stored at different memory addresses. How can == return true then?
The Java Integer Cache
To understand this, we have to go back in time to 2004, to the release of Java 5.
At the time, Java engineers noticed that developers used small numbers like 0, 1, 10, or 100 constantly. They were used everywhere as array indexes, loop counters, and default statuses.
Creating a brand new object in memory takes a tiny bit of processing time and space. Doing this millions of times for the same small numbers was causing unnecessary performance and memory problems in large applications.
To fix this, Java introduced the Integer Cache.
When the JVM starts up, before your program even begins to run, it automatically creates and caches an Integer object for every number from -128 to 127.
How Autoboxing Triggers the Cache
When you write Integer a = 100;, Java performs something called autoboxing. It automatically converts your primitive number into an Integer object. Behind the scenes, the code actually compiles to this:
Integer a = Integer.valueOf(100);
If we peek directly into the Java source code for that valueOf() method, we can see:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
The logic is simple:
- Is the number between -128 and 127?
- If yes, hand back the pre-made, cached object.
- If no, create a brand new object.
Because 100 falls inside the cache limits, both Integer a and Integer b are handed a reference to the exact same cached object. They share the exact same memory address, so a == b returns true.
However, 200 is outside the cache limits. When you ask for 200, Java creates two fresh, separate objects at different memory addresses. Therefore, x == y returns false.
How to Fix It
This feature is cause of some bugs in some real world projects. You might write an application and test it using small IDs (like 5 or 10), and all your tests pass. Then, you push it to production, and the moment a database ID hits 128, the app mysteriously crashes unexpectedly.
The Golden Rule: Never use == to compare objects in Java.
You should always use the .equals() method. For Integer objects, the .equals() method is designed to ignore memory addresses and strictly compare the underlying numerical values:
Integer x = 200;
Integer y = 200;
System.out.println(x.equals(y));
Prints true.
Two Bonus things to Watch Out For
Before you go refactoring your code, keep these two extra rules in mind:
1. Primitives are Safe
If you are comparing basic primitive int variables instead of Integer objects, the == operator works perfectly.
int x = 200;
int y = 200;
System.out.println(x == y); // Prints: true
Because primitives are raw values and not objects, there are no memory addresses or cache lookups involved.
2. Avoid the new Keyword
If you explicitly create an integer using the constructor, you bypass the cache entirely:
Integer a = new Integer(100);
Integer b = new Integer(100);
System.out.println(a == b); // Prints: false
By using new Integer(), you force Java to create a brand new spot in memory, missing out on all the performance benefits of the cache. This is such a bad practice that Java officially deprecated this constructor in Java 9, and marked it for future removal under JEP 390: Warnings for Value-Based Classes.
No comments yet — be the first!