Back to Blog
Java

Java Comparison Operators Explained

java comparison operators: Learn how Java comparison operators work on primitives and objects, avoid common pitfalls with equality, and choose the right comparison str...

Java operatorsequalityrelational operatorsComparablenull safety
Diagram showing Java comparison operators and their behavior on primitives and objects

Java comparison operators are used to evaluate the relationship between two values. The language provides six operators: <, <=, >, >=, ==, and !=. They work on primitive numeric types, booleans (only == and !=), and objects (only == and !=, which compare references). Understanding exactly what each operator does is essential because the same symbol can have different semantics depending on the operand types.

The Relational Operators and Their Return Type

The relational operators <, <=, >, and >= are defined for numeric primitive types: byte, short, int, long, float, and double. They also work on char because a character is represented as a numeric value. These operators always return a boolean result.

int a = 10; int b = 20; boolean less = a < b; // true boolean lessOrEqual = a <= b; // true boolean greater = a > b; // false boolean greaterOrEqual = a >= b; // false

When the operands have different numeric types, Java applies binary numeric promotion before evaluating the comparison. For example, comparing an int and a long promotes the int to long. This promotion can cause surprising behavior with large unsigned-like values, but for typical ranges it is straightforward.

Relational operators do not work on boolean values. You cannot write true < false; the compiler rejects it. They also do not work on reference types unless you use a wrapper class and rely on unboxing, which is error-prone and not recommended.

Equality Operators: == and !=

The equality operators == and != have two distinct behaviors depending on whether the operands are primitives or references.

For primitives, == compares the actual values. This works for numeric types and boolean. For char, it compares the numeric code points.

int x = 5; int y = 5; System.out.println(x == y); // true boolean flag1 = true; boolean flag2 = true; System.out.println(flag1 == flag2); // true

For reference types, == compares object references, not the contents of the objects. Two distinct objects with identical fields are not equal under ==. This is a common source of bugs when developers expect value equality.

String s1 = new String("hello"); String s2 = new String("hello"); System.out.println(s1 == s2); // false

The != operator is simply the negation of ==. It returns true when the operands are not equal under the same rules.

Comparing Floats and Doubles

Floating-point comparisons require care because of the way binary floating-point represents decimal values. Direct == on float or double can fail for values that should be mathematically equal but differ by a tiny rounding error.

double a = 0.1 + 0.2; double b = 0.3; System.out.println(a == b); // false

The expression 0.1 + 0.2 produces a binary value slightly different from 0.3. For most applications, you should compare with a tolerance (epsilon) rather than exact equality.

public static boolean nearlyEqual(double left, double right, double epsilon) { return Math.abs(left - right) < epsilon; }

The choice of epsilon depends on the magnitude of the values and the precision required. For currency or other exact decimal calculations, use BigDecimal instead of double.

The Float.compare and Double.compare methods are useful when you need a total ordering that handles NaN and signed zero consistently. They return an int suitable for sorting or for use in a Comparator.

Reference Equality vs .equals()

The most frequent mistake with == on objects is assuming it compares the logical content. For strings, arrays, and custom classes, you must use the .equals() method to compare values.

String a = "hello"; String b = "hello"; System.out.println(a == b); // true, but only because both refer to the same interned string String c = new String("hello"); System.out.println(a == c); // false System.out.println(a.equals(c)); // true

String literals are interned, so == may return true for two literals with the same content. This is an implementation detail that should not be relied upon. Always use .equals() for string content comparison.

For custom classes, override equals() and hashCode() together to define value equality. If you do not override equals(), the default implementation uses ==, which compares references.

Comparing Objects with Comparable and Comparator

When you need to order objects, the relational operators are not available. Instead, implement the Comparable interface and override compareTo(), or provide a Comparator.

The compareTo() method returns a negative integer, zero, or a positive integer depending on whether the current object is less than, equal to, or greater than the argument.

public class Person implements Comparable<Person> { private final String name; public Person(String name) { this.name = name; } @Override public int compareTo(Person other) { return this.name.compareTo(other.name); } }

A Comparator is useful when you need multiple ordering strategies or when the class does not implement Comparable.

Comparator<Person> byName = Comparator.comparing(Person::getName);

Both approaches produce an ordering that can be used with Collections.sort(), Arrays.sort(), and sorted collections like TreeSet.

Null Safety and Common Mistakes

Comparing a reference with == to null is the standard way to check for a null reference. However, calling .equals() on a null reference throws a NullPointerException.

String s = null; if (s == null) { System.out.println("s is null"); } // s.equals("x") would throw NPE

When comparing a known non-null constant with a possibly null variable, put the constant on the left side of .equals() to avoid the exception.

if ("hello".equals(s)) { // safe even if s is null }

Relational operators on wrapper types such as Integer involve unboxing. If the wrapper is null, unboxing throws a NullPointerException.

Integer n = null; if (n < 10) { // throws NullPointerException }

Always unbox explicitly or use a null check before comparing wrapper types.

Operator Precedence and Parentheses

Comparison operators have lower precedence than arithmetic operators but higher precedence than logical operators. For example, a + b < c is evaluated as (a + b) < c. The equality operators have lower precedence than relational operators, so a < b == c is parsed as (a < b) == c, which is rarely what you want.

int a = 1, b = 2, c = 3; boolean result = a < b == c; // parsed as (a < b) == c, which compares boolean to int -> compile error

To avoid confusion and improve readability, use parentheses even when the precedence is clear. The compiler does not require them, but future maintainers will appreciate the clarity.

Performance Considerations

Comparison operators on primitives are extremely fast and do not allocate objects. The JVM can optimize these operations well. The main performance concern is not the operator itself but the surrounding logic, such as repeated comparisons inside loops or unnecessary autoboxing.

When you use wrapper types in comparisons, the JVM must unbox them, which adds a small overhead. If performance is critical, prefer primitive types over wrappers.

For object comparisons, compareTo() and Comparator implementations may be called frequently during sorting. A well-designed comparison that avoids expensive field access can reduce sorting time. Avoid creating new objects inside a comparison method.

There is no meaningful performance difference between == and .equals() for strings because string equality checks length and then character by character. The overhead is minimal unless the strings are very long.

Choosing the Right Comparison Approach

The decision of which comparison operator or method to use depends on the operand types and the semantics you need.

  • Use <, <=, >, >= only for numeric primitives and char.
  • Use == and != for primitive values and for reference identity checks, including null checks.
  • Use .equals() for value equality on objects, especially strings and custom classes.
  • Use Float.compare or Double.compare when you need a total order that handles NaN and signed zero.
  • Implement Comparable or provide a Comparator when you need to sort or order objects.

For floating-point values, avoid direct == unless you are comparing against a constant that is exactly representable, such as 0.0 or 1.0. In all other cases, use an epsilon-based comparison or BigDecimal for exact decimal arithmetic.

A common pattern for a robust compareTo on a double field is to use Double.compare rather than subtracting values, because subtraction can overflow or produce NaN.

@Override public int compareTo(Item other) { return Double.compare(this.price, other.price); }

This handles edge cases correctly and is the recommended approach when implementing natural ordering for floating-point fields.

java comparison operators: Practical Usage and Code Examples | RYUSLOG DEV