Java Operators
Operators are the symbols that tell Java how to combine, compare, and manipulate values. Every calculation, condition, and loop you write depends on them, so understanding exactly how each one behaves — including its quirks around types, precedence, and evaluation order — is essential to writing correct Java code. This lesson covers every major category of Java operator: arithmetic, relational, logical, bitwise, assignment, and the ternary operator, along with the subtle mistakes that trip up even experienced developers.
Overview: How Operators Work
An operator is a symbol that performs an operation on one or more values, called operands, and produces a result. 3 + 4 is an expression: + is the operator, 3 and 4 are the operands, and the expression evaluates to the value 7. Operators are classified by how many operands they take: unary operators act on one operand (like -x or x++), binary operators act on two (like a + b), and Java has exactly one ternary operator, which takes three operands (condition ? a : b).
Every operator expression has a type and a value, determined by the compiler at compile time. When operands of different numeric types are mixed, Java applies binary numeric promotion: smaller types are automatically widened to match the larger one, following the order byte/short/char → int → long → float → double. This is why 5 / 2 (both int) evaluates to 2, but 5 / 2.0 (an int and a double) evaluates to 2.5 — the int is promoted to double before the division happens.
Java also defines strict precedence (which operator binds tighter) and associativity (the order operators of equal precedence are applied) for every operator. Multiplicative operators (*, /, %) bind tighter than additive ones (+, -), which bind tighter than relational operators, which bind tighter than logical operators. Most binary operators are left-associative (evaluated left to right), while assignment operators are right-associative. When in doubt, use parentheses — they cost nothing and make your intent explicit to both the compiler and the next person reading your code.
Syntax
The general form of an operator expression is:
operand1 operator operand2 // binary
operator operand // unary
condition ? valueIfTrue : valueIfFalse // ternary
Java’s operators fall into these families:
| Category | Operators | Purpose |
|---|---|---|
| Arithmetic | + - * / % |
Perform math on numbers |
| Unary | + - ++ -- ! |
Act on a single operand |
| Relational | == != > < >= <= |
Compare two values, produce a boolean |
| Logical | && || ! |
Combine boolean expressions |
| Bitwise | & | ^ ~ << >> >>> |
Operate on individual bits of integers |
| Assignment | = += -= *= /= %= &= |= ^= <<= >>= |
Store a value in a variable |
| Ternary | ?: |
Inline conditional expression |
Examples
Example 1: Arithmetic Operators
public class Main {
public static void main(String[] args) {
int a = 17;
int b = 5;
System.out.println("a + b = " + (a + b));
System.out.println("a - b = " + (a - b));
System.out.println("a * b = " + (a * b));
System.out.println("a / b = " + (a / b));
System.out.println("a % b = " + (a % b));
double x = 17.0;
double y = 5.0;
System.out.println("x / y = " + (x / y));
}
}
Output:
a + b = 22
a - b = 12
a * b = 85
a / b = 3
a % b = 2
x / y = 3.4
Because a and b are both int, a / b performs integer division and truncates the fractional part (17 ÷ 5 = 3.4, but the result is 3). The % (modulo) operator gives the remainder of that division: 17 % 5 = 2. Once the operands are double, division keeps the fractional part.
Example 2: Relational, Logical, and Ternary Operators
public class Main {
public static void main(String[] args) {
int age = 20;
boolean hasId = true;
boolean canEnter = (age >= 18) && hasId;
System.out.println("canEnter: " + canEnter);
int score = 72;
String grade = (score >= 90) ? "A" : (score >= 70) ? "B" : "C";
System.out.println("grade: " + grade);
int temp = 15;
boolean isFreezingOrHot = (temp <= 0) || (temp >= 35);
System.out.println("isFreezingOrHot: " + isFreezingOrHot);
System.out.println("5 == 5.0: " + (5 == 5.0));
System.out.println("!hasId: " + !hasId);
}
}
Output:
canEnter: true
grade: B
isFreezingOrHot: false
5 == 5.0: true
!hasId: false
The relational operators (>=, <=, ==) always produce a boolean. Logical && and || combine booleans and use short-circuit evaluation: in (age >= 18) && hasId, if the first operand were false, Java would never evaluate hasId at all. The ternary operator ?: is chained here to pick between three grades based on score; it is essentially a compact if / else that produces a value rather than executing a block.
Example 3: Compound Assignment, Increment/Decrement, and Bitwise Operators
public class Main {
public static void main(String[] args) {
int count = 10;
count += 5;
count -= 2;
count *= 3;
count /= 4;
System.out.println("count: " + count);
int i = 5;
System.out.println("i++ gives: " + (i++));
System.out.println("after i++: " + i);
System.out.println("++i gives: " + (++i));
int flags = 0b1010;
int mask = 0b0110;
System.out.println("AND: " + (flags & mask));
System.out.println("OR: " + (flags | mask));
System.out.println("XOR: " + (flags ^ mask));
System.out.println("Left shift: " + (flags << 1));
System.out.println("Right shift: " + (flags >> 1));
}
}
Output:
count: 9
i++ gives: 5
after i++: 6
++i gives: 7
AND: 2
OR: 14
XOR: 12
Left shift: 20
Right shift: 5
Compound assignment operators like += combine an operation with an assignment and implicitly cast the result back to the variable’s type. i++ (post-increment) returns the current value of i before incrementing it, so it prints 5 and only afterward becomes 6; ++i (pre-increment) increments first and then returns the new value, 7. The bitwise operators work on the binary representation of integers: 0b1010 is 10 and 0b0110 is 6, so 10 & 6 = 2, 10 | 6 = 14, and 10 ^ 6 = 12. Shifting 10 left by one bit doubles it to 20; shifting right by one bit halves it to 5.
Under the Hood: Evaluation Order and Type Promotion
The Java Language Specification guarantees that operands are evaluated left to right, and each operand is fully evaluated (including any side effects, like method calls or ++) before the next one starts. This matters when expressions have side effects: in a[i++] = i++, the left-hand index is computed before the right-hand value, in a well-defined order — unlike some languages (such as C), where this order is unspecified.
When the compiler sees a binary numeric operator, it performs these steps: (1) if either operand is double, the other is widened to double; (2) otherwise if either is float, the other widens to float; (3) otherwise if either is long, the other widens to long; (4) otherwise both operands are widened to int (this is why arithmetic on byte or short variables always produces an int result, and must be cast back explicitly if you want to store it in a smaller type).
For && and ||, the JVM generates conditional branch bytecode rather than always evaluating both sides — this is short-circuit evaluation, and it is not just an optimization but a language guarantee you can rely on for safety, such as writing if (obj != null && obj.isValid()) without risking a NullPointerException. The non-short-circuiting bitwise versions & and | can also be applied to booleans, but they always evaluate both operands, so they are rarely what you want for control-flow logic.
Common Mistakes
Mistake 1: Expecting Integer Division to Produce a Fraction
Wrong:
public class Main {
public static void main(String[] args) {
int total = 5;
int items = 2;
double average = total / items;
System.out.println("Average: " + average);
}
}
Output:
Average: 2.0
The division total / items is computed entirely in int arithmetic before the result is assigned to the double variable, so the fractional part is already lost — assigning the truncated 2 to a double just prints it as 2.0, not the 2.5 you probably wanted.
Corrected:
public class Main {
public static void main(String[] args) {
int total = 5;
int items = 2;
double average = total / (double) items;
System.out.println("Average: " + average);
}
}
Output:
Average: 2.5
Casting one operand to double before the division forces the whole expression into floating-point arithmetic, preserving the fractional result.
Mistake 2: Using == to Compare Strings (or Objects)
Wrong:
public class Main {
public static void main(String[] args) {
String a = new String("hello");
String b = new String("hello");
System.out.println("a == b: " + (a == b));
}
}
Output:
a == b: false
For objects (including String), == compares references — whether two variables point to the exact same object in memory — not their contents. a and b hold equal text but are two distinct String objects, so == reports false.
Corrected:
public class Main {
public static void main(String[] args) {
String a = new String("hello");
String b = new String("hello");
System.out.println("a.equals(b): " + a.equals(b));
}
}
Output:
a.equals(b): true
Use .equals() (or Objects.equals(), which is null-safe) whenever you want to compare the content of objects. Reserve == for primitives and for the rare cases where you deliberately want reference identity.
Best Practices
- Use parentheses to make precedence explicit in any expression mixing more than one operator family, even when you’re sure of the default order — it prevents bugs and helps readers.
- Cast explicitly (
(double) x) when you need floating-point division, rather than relying on incidental promotion. - Always use
.equals()for comparingStrings and other objects; save==for primitives and reference-identity checks. - Avoid cramming multiple side-effecting increments (
i++,++i) into a single expression — split them into separate statements for clarity, even though Java’s evaluation order is well-defined. - Prefer
&&and||over&and|for boolean logic so you get short-circuit evaluation and avoid unnecessary (or unsafe) evaluation of the right-hand side. - Use the ternary operator only for short, simple expressions; nested ternaries hurt readability and are better written as
if / else. - When working with bit flags, name your masks with descriptive constants instead of magic numbers.
Practice Exercises
- Exercise 1: Write a program that declares two
intvariables, computes their sum, difference, product, quotient, and remainder, and prints each with a label. Then repeat the division using a cast so it prints a precise decimal result. - Exercise 2: Write a program that takes a person’s age as an
intand uses the ternary operator to assign aStringcategory:"child"for under 13,"teen"for 13–19, and"adult"for 20 and over. Print the result for at least three different ages. - Exercise 3: Declare two
intvariables representing bit flags (for example, permissionsREAD = 0b100,WRITE = 0b010,EXECUTE = 0b001). Use bitwise|to combine two of them into a single value, then use&to check whether a specific flag is set within that combined value, printingtrueorfalse.
Summary
- Java operators fall into arithmetic, relational, logical, bitwise, assignment, and ternary categories, each producing a typed value.
- Integer division truncates; mix in a
doubleoperand (often via an explicit cast) when you need a fractional result. - Relational operators produce
boolean; logical&&and||short-circuit, skipping the right operand when the result is already determined. - Compound assignment operators (
+=,*=, etc.) combine an operation with an implicit cast back to the variable’s type. - Post-increment (
i++) returns the old value; pre-increment (++i) returns the new value — know the difference before embedding them in expressions. - Always compare objects, including
Strings, with.equals();==checks reference identity, not content. - When precedence isn’t obvious at a glance, add parentheses — it costs nothing and prevents subtle bugs.
