Skip to content

Operators in Java

You have probably already used operators like +, ==, and > without thinking too deeply about them. Today you are going to understand every category of operator Java has, how they work at the bit level, which ones trip up experienced developers in interviews, and exactly why they behave the way they do. By the end of this article you will be able to trace through any operator expression confidently, even the tricky increment puzzles that interviewers love.

What Exactly Is an Operator?

An operator tells Java what action to perform. In the expression 5 + 3, the + is the operator. It says: take these two things and add them. The things you operate on are called operands. Your operands can be constants like 5 and 3, or they can be variables like a and b.

An expression is any combination of one or more operands and zero or more operators. Something as simple as 5 is an expression. Something as complex as a + a++ + ++a * --a + a-- is also an expression. The rules for evaluating that second one are what you are about to learn.

Java has nine categories of operators. Some of them you will find trivially easy if you have used any other programming language. A few of them, specifically bitwise operators and shift operators, deserve serious attention because they come up constantly in data structures and algorithm problems and in interviews.


Arithmetic Operators

Arithmetic operators do the math you learned in school: addition, subtraction, multiplication, division, and modulus. They work exactly the same way in Java as in every other language you have seen.

java
int a = 5, b = 2;

System.out.println(a + b);   // 7   addition
System.out.println(a - b);   // 3   subtraction
System.out.println(a * b);   // 10  multiplication
System.out.println(a / b);   // 2   integer division truncates toward zero
System.out.println(a % b);   // 1   modulus: the remainder after division

The one thing to keep in mind here is integer division. When both operands are integers, Java does not give you a decimal result. 5 / 2 gives you 2, not 2.5. The fractional part is simply discarded. If you need the decimal result, at least one operand must be a double or float.


Relational Operators

Relational operators compare two operands and always return a boolean: either true or false. There is no third option.

java
int a = 4, b = 7;

System.out.println(a == b);   // false   is a equal to b?
System.out.println(a != b);   // true    is a not equal to b?
System.out.println(a > b);    // false   is a greater than b?
System.out.println(a < b);    // true    is a less than b?
System.out.println(a >= b);   // false   is a greater than or equal to b?
System.out.println(a <= b);   // true    is a less than or equal to b?

Every single one of these returns true or false. No relational expression can return a number. That is a useful guarantee: wherever you see a relational operator, you know you are dealing with a boolean result.

One common mistake beginners make is using == to compare String values. For primitive types like int, == compares values directly. For objects, == compares references, meaning it checks whether both variables point to the exact same object in memory, not whether they hold equal content. For Strings, always use .equals().


Logical Operators and Short Circuit Evaluation

Logical operators combine two or more conditions and return a boolean. You have three of them in Java: logical AND (&&), logical OR (||), and logical NOT (!).

How AND Works

For && to return true, both conditions on its left and right must be true. If even one is false, the whole expression is false.

java
int a = 4, b = 7;

// Both must be true for && to return true
boolean r1 = (a < 3) && (a != b);   // false: (a < 3) is false, so result is false
boolean r2 = (a > 3) && (a != b);   // true:  both are true

Here is the crucial behavior called short circuit evaluation. In the first line, Java evaluates (a &lt; 3) and gets false. At that point Java already knows the whole && expression must be false, because no matter what the second condition says, false AND anything is false. So Java skips the second condition entirely. It never runs (a != b).

This matters enormously in practice. Consider this:

java
if (obj != null && obj.getValue() > 0) {
    process(obj);
}

If obj is null, the first condition is false and Java short circuits. It never tries to call obj.getValue(), which would have crashed with a NullPointerException. Short circuit evaluation makes this pattern safe. Without it, you would need to nest the null check in a separate outer if.

How OR Works

For || to return true, at least one condition must be true. If the very first condition is true, Java already knows the whole expression is true and skips the rest.

java
boolean r3 = (a > 3) || (a != b);   // true:  first is true, second is skipped
boolean r4 = (a < 3) || (a != b);   // true:  first is false, second evaluated, true

Non Short Circuit Variants

Java also has & and | as logical operators (not just bitwise). These always evaluate both sides regardless of the first result. You would use them only when the right side has a deliberate side effect that must always run. In most code, && and || are what you want.


Unary Operators

Unary operators work on a single operand. All the operators you have seen so far need two operands: a + b, a &gt; b. Unary operators only need one.

Java has five unary operators: increment (++), decrement (--), unary plus (+), unary minus (-), and logical not (!).

Increment and Decrement: The Interview Trap

Increment and decrement are where interviews get interesting. Both have two forms: postfix and prefix.

a++   postfix increment   (the ++ comes after)
++a   prefix increment    (the ++ comes before)

The rule is simple to state but easy to confuse under pressure:

Postfix (a++): Return the current value first, then increment.
Prefix (++a): Increment first, then return the new value.

Walk through this carefully:

java
int a = 5;

System.out.println(a++);   // prints 5 — returns 5, THEN a becomes 6
System.out.println(++a);   // prints 7 — a goes from 6 to 7, THEN returns 7
System.out.println(a--);   // prints 7 — returns 7, THEN a becomes 6
System.out.println(--a);   // prints 5 — a goes from 6 to 5, THEN returns 5

Step through this one more time to be sure:

  • Start: a = 5
  • a++: returns 5, then a becomes 6. Prints 5.
  • ++a: a is 6, increments to 7, returns 7. Prints 7.
  • a--: a is 7, returns 7, then a becomes 6. Prints 7.
  • --a: a is 6, decrements to 5, returns 5. Prints 5.

Unary Plus and Minus

+a leaves the value positive. -a negates it. If a = 5, then -a gives -5.

Logical Not

! flips a boolean. If flag = true, then !flag is false.

java
boolean flag = true;
System.out.println(!flag);   // false

Logical not is a unary operator because it operates on only one operand.


Assignment Operators

Assignment operators assign a value to a variable. The basic one is =. The compound versions combine a mathematical operation with assignment.

java
int a = 5;       // basic assignment: a is now 5
a += 4;          // same as a = a + 4;  now a is 9
a -= 3;          // same as a = a - 3;  now a is 6
a *= 2;          // same as a = a * 2;  now a is 12
a /= 4;          // same as a = a / 4;  now a is 3
a %= 2;          // same as a = a % 2;  now a is 1

Each compound assignment operator is pure shorthand. a += 4 expands to exactly a = a + 4. You can do this with any arithmetic operator: +=, -=, *=, /=, %=.


Bitwise Operators

Now things get genuinely interesting. Bitwise operators work directly on the binary representation of integers, bit by bit. The processor supports them natively, so they are extremely fast. They show up constantly in DSA problems, low level programming, and interviews.

You have four bitwise operators: AND (&), OR (|), XOR (^), and NOT (~).

To understand them, you need to think in binary. Let's use a = 4 and b = 6.

In binary (using four bits for clarity):

  • 4 is 0100
  • 6 is 0110

Bitwise AND (&)

Both bits must be 1 for the result to be 1. Otherwise the result is 0.

0100   (4)
0110   (6)
----
0100   result
java
System.out.println(4 & 6);   // 4

Bitwise OR (|)

If either bit is 1, the result is 1. Both must be 0 for the result to be 0.

0100   (4)
0110   (6)
----
0110   result
java
System.out.println(4 | 6);   // 6

Bitwise XOR (^)

If the bits are different, the result is 1. If they are the same, the result is 0.

0100   (4)
0110   (6)
----
0010   result
java
System.out.println(4 ^ 6);   // 2

Bitwise NOT (~): The Surprising One

Bitwise NOT flips every single bit: 0 becomes 1 and 1 becomes 0. Seems simple. But the result is often surprising if you forget how Java stores integers.

java
int a = 4;
System.out.println(~a);   // prints -5

Why does flipping the bits of 4 give you negative 5? Here is why.

Java integers are always signed. There is no unsigned int in Java. The most significant bit, the leftmost bit in a 32 bit integer, is the sign bit. If it is 0, the number is positive. If it is 1, the number is negative.

The value 4 in binary is:

0000 0000 0000 0000 0000 0000 0000 0100

The most significant bit is 0, so this is positive 4.

After bitwise NOT, every bit flips:

1111 1111 1111 1111 1111 1111 1111 1011

Now the most significant bit is 1, so this is a negative number. To find which negative number, read it as two's complement. The shortcut formula is:

~n = -(n + 1)

So ~4 = -(4 + 1) = -5.

You can verify this. To represent 5 in binary: 0101. First complement (flip bits): 1010. Second complement (add 1): 1011. That is the two's complement representation of -5, and it matches exactly what we computed for ~4.

The complete table for a quick sanity check:

java
int a = 4;   // 0100
int b = 6;   // 0110

System.out.println(a & b);   // 4   AND: 0100
System.out.println(a | b);   // 6   OR:  0110
System.out.println(a ^ b);   // 2   XOR: 0010
System.out.println(~a);      // -5  NOT: all bits flipped, sign bit becomes 1

Shift Operators

Shift operators move all the bits of a number to the left or right by a specified number of positions. There are three: left shift (&lt;&lt;), signed right shift (&gt;&gt;), and unsigned right shift (&gt;&gt;&gt;).

A quick memory trick: look at where the arrow points. &lt;&lt; points left. &gt;&gt; points right. You never need to memorize which is which.

Left Shift (&lt;&lt;)

Moving bits left is equivalent to multiplying by a power of 2.

Take a = 4, which is 0100 in binary.

Left shift by 1 means every bit moves one position to the left. The rightmost position gets filled with 0. Any bit that falls off the left edge is discarded.

0100   (4)
1000   (8, after left shift by 1)

Left shift by 2 would take 0100 to 10000, which is 16.

java
int n = 4;
System.out.println(n << 1);   // 8   (4 * 2)
System.out.println(n << 2);   // 16  (4 * 4)

The vacant positions on the right are always filled with 0. This is why there is no such thing as an unsigned left shift. The bit that matters for signedness is the most significant bit on the far left. The least significant bit on the right, which gets filled in, carries no sign information. So there is nothing to distinguish between signed and unsigned here.

Signed Right Shift (&gt;&gt;)

Moving bits right is equivalent to dividing by a power of 2.

Take n = 4 which is ...0100. Right shift by 1: every bit moves one position right. The rightmost bit falls off and is discarded. The leftmost position gets filled with a copy of the sign bit (the original most significant bit).

For a positive number, the sign bit is 0, so you fill with 0:

0100   (4)
0010   (2, after right shift by 1)

For a negative number, say -4, the sign bit is 1, so you fill with 1. This preserves the sign, which is why this is called the signed right shift.

java
int n = 4;
System.out.println(n >> 1);     // 2

int neg = -4;
System.out.println(neg >> 1);   // -2   sign bit preserved

Unsigned Right Shift (&gt;&gt;&gt;)

The unsigned right shift works exactly like the signed right shift, except it always fills the leftmost position with 0, no matter what the original sign bit was.

This means if you use it on a negative number, the result will be a large positive number because the 1 in the sign bit gets replaced by a 0.

java
int neg = -4;
System.out.println(neg >> 1);    // -2           signed: fills with 1
System.out.println(neg >>> 1);   // 2147483646   unsigned: fills with 0, flips sign

The result 2147483646 looks bizarre until you realize what happened: a 32 bit number that had a 1 in the most significant bit now has a 0 there, making it a huge positive number.

Summary of Shift Operators

OperatorDirectionFill BitEffect
a &lt;&lt; kLeftRight side fills with 0Multiply by 2 to the power k
a &gt;&gt; kRightLeft side fills with sign bitDivide by 2 to the power k, sign preserved
a &gt;&gt;&gt; kRightLeft side always fills with 0Unsigned divide, may flip sign
java
int n = 4;
System.out.println(n << 1);    // 8   multiply by 2
System.out.println(n << 2);    // 16  multiply by 4
System.out.println(n >> 1);    // 2   divide by 2
System.out.println(n >>> 1);   // 2   same as >> for positive numbers

Why Shift Operators Matter in DSA

Bit manipulation using shift operators is a fundamental technique in competitive programming and technical interviews. Here are patterns you will see repeatedly:

java
// Check if n is even (last bit is 0 means even)
boolean isEven = (n & 1) == 0;

// Check if n is a power of 2
boolean isPowerOf2 = n > 0 && (n & (n - 1)) == 0;

// Toggle the kth bit (flip it)
int toggled = n ^ (1 << k);

// Clear the kth bit (force it to 0)
int cleared = n & ~(1 << k);

// Set the kth bit (force it to 1)
int set = n | (1 << k);

These patterns are worth understanding deeply, not just memorizing. Once you see the underlying logic, you can derive them on the fly.


Ternary Operator

The ternary operator is a compact way to write an if else that produces a value. The format is:

condition ? valueIfTrue : valueIfFalse

Think of the question mark as asking a yes or no question. The colon separates the yes answer from the no answer.

java
int a = 4, b = 5;

// Long form with if else
int max;
if (a > b) {
    max = a;
} else {
    max = b;
}

// Exactly equivalent using ternary
int max2 = (a > b) ? a : b;   // 5

The ternary operator is an expression, meaning it produces a value you can assign or pass around. The if else statement is not an expression, it is a statement. That is why ternary can appear in places where a statement cannot, like inside a System.out.println() call or as an argument to a method.

Keep ternary expressions simple. Nested ternaries become nearly impossible to read and should be avoided.


The instanceof Operator

instanceof is a type comparison operator. It checks whether an object is an instance of a particular class or any of its subclasses, and returns a boolean.

Imagine you have an inheritance hierarchy:

java
class ParentClass {}
class ChildClass1 extends ParentClass {}
class ChildClass2 extends ParentClass {}

Now consider these checks:

java
Object obj = new ChildClass2();

System.out.println(obj instanceof ChildClass2);   // true
System.out.println(obj instanceof ChildClass1);   // false
System.out.println(obj instanceof ParentClass);   // true: child IS a parent

The third check returns true because ChildClass2 extends ParentClass. An object of a child class is also an instance of the parent class. This is the "is a" relationship in object oriented programming.

The most common place you will see instanceof is when you receive a reference typed as a broad parent type (like Object) and need to figure out what the actual runtime type is before doing something specific with it.

java
void process(Object obj) {
    if (obj instanceof String) {
        String s = (String) obj;
        System.out.println(s.toUpperCase());
    } else if (obj instanceof Integer) {
        Integer i = (Integer) obj;
        System.out.println(i * 2);
    }
}

Java 16 and later supports pattern matching with instanceof, which combines the check and the cast in one step:

java
void process(Object obj) {
    if (obj instanceof String s) {
        System.out.println(s.toUpperCase());   // s is already typed as String
    } else if (obj instanceof Integer i) {
        System.out.println(i * 2);
    }
}

Operator Precedence and Associativity

When an expression contains more than one operator, Java needs rules to determine which to evaluate first. Those rules are called operator precedence.

The classic example: 5 + 2 * 3. The answer is 11, not 21. Multiplication has higher precedence than addition, so 2 * 3 is evaluated first to get 6, then 5 + 6 gives 11.

You do not need to memorize the complete precedence table, but knowing the broad order is useful: unary operators are evaluated before multiplicative (*, /, %), which come before additive (+, -), which come before relational (&lt;, &gt;, ==), which come before logical (&&, ||), which come before assignment (=).

Associativity

What happens when two operators have the same precedence, like * and / in 10 * 2 / 2?

Associativity answers that. Most arithmetic operators are left to right: you evaluate from the left. So 10 * 2 / 2 becomes (10 * 2) / 2 = 20 / 2 = 10.

Assignment is right to left. That is why this works:

java
int a, b, c;
a = b = c = 5;   // evaluates right to left: c=5, then b=5, then a=5

Key Precedence Table

CategoryOperatorsAssociativity
Postfixexpr++ expr--Left to right
Unary++expr --expr +expr -expr ~ !Right to left
Multiplicative* / %Left to right
Additive+ -Left to right
Shift&lt;&lt; &gt;&gt; &gt;&gt;&gt;Left to right
Relational&lt; &gt; &lt;= &gt;= instanceofLeft to right
Equality== !=Left to right
Bitwise AND&Left to right
Bitwise XOR^Left to right
Bitwise OR|Left to right
Logical AND&&Left to right
Logical OR||Left to right
Ternary? :Right to left
Assignment= += -= *= /= %=Right to left

Higher rows have higher precedence and are evaluated first.


The Classic Interview Question: Tracing Through Increment Expressions

Interviewers love to give you an expression that mixes a++, ++a, --a, and a-- with arithmetic and ask you to compute the result. The trick is knowing the correct approach.

The rule: evaluate and substitute each increment or decrement in the order they appear left to right, keeping track of the current value of a as you go. After substituting all values, apply operator precedence.

Let's trace the example step by step:

java
int a = 4;
int result = a + a++ + ++a * --a + a--;

Start: a = 4.

Go left to right and substitute:

  1. a — read current value. Substitute 4. a is still 4.
  2. a++ — postfix: substitute current value first. Substitute 4. Then increment: a becomes 5.
  3. ++a — prefix: increment first. a goes from 5 to 6. Substitute 6.
  4. --a — prefix: decrement first. a goes from 6 to 5. Substitute 5.
  5. a-- — postfix: substitute current value first. Substitute 5. Then decrement: a becomes 4.

The expression is now: 4 + 4 + 6 * 5 + 5

Now apply operator precedence. Multiplication comes before addition:

6 * 5 = 30
4 + 4 + 30 + 5 = 43
java
System.out.println(result);   // 43
System.out.println(a);        // 4, back where it started

The value of a ends at 4 because the increments and decrements balanced out perfectly.

Practice this technique on your own with a new expression. Try:

java
int x = 2;
int z = ++x + x++ / x++ - 1;

Substitute left to right: ++x increments x to 3, substitutes 3. x++ substitutes 3, x becomes 4. x++ substitutes 4, x becomes 5.

Expression becomes: 3 + 3 / 4 - 1.

Apply precedence: division first. But both operands are integers, so 3 / 4 = 0. Then: 3 + 0 - 1 = 2.

The key insight is always the same: never try to evaluate the whole expression at once. Substitute operand by operand from left to right, then apply precedence rules to the numbers you have collected.


All Nine Operator Categories at a Glance

CategoryOperatorsKey Point
Arithmetic+ - * / %Integer division truncates toward zero
Relational== != &lt; &gt; &lt;= &gt;=Always returns boolean; use .equals() for objects
Logical&& || !Short circuit evaluation skips unnecessary work
Unary++ -- + - !Pre vs post matters: prefix changes first, postfix returns first
Assignment= += -= *= /= %=Compound forms expand to a = a &lt;op&gt; value
Bitwise& | ^ ~Work on individual bits; ~n = -(n+1)
Shift&lt;&lt; &gt;&gt; &gt;&gt;&gt;Left is multiply by power of 2; right is divide
Ternary? :Compact inline if else; produces a value
Type comparisoninstanceofChecks object type; returns boolean

Bitwise and shift operators are the ones most developers underinvest in early on, and they are exactly the ones that come up most in DSA problems and interviews. The rest you will use every single day and develop intuition for naturally.

Spend extra time tracing increment and decrement expressions by hand. Write a few yourself, predict the output before running the code, then verify. There is no shortcut to building that intuition other than doing the work.