Java Operators

June 22, 20265 min readUpdated 8/20/2026

Operators are the symbols that do things to values. Most of them behave exactly as you would guess from school arithmetic. This post covers all of them quickly, then spends its time on the four that do not behave as you would guess — which is where the bugs are.

Arithmetic

int a = 17, b = 5;

System.out.println(a + b);    // 22
System.out.println(a - b);    // 12
System.out.println(a * b);    // 85
System.out.println(a / b);    // 3   <- not 3.4
System.out.println(a % b);    // 2   <- remainder

Trap 1: integer division truncates. 17 / 5 is 3, not 3.4, because both operands are int and Java produces an int. The fractional part is discarded, not rounded.

int total = 17, count = 5;

double wrong = total / count;               // 3.0  — division already happened in int
double right = (double) total / count;      // 3.4  — one cast before the division fixes it

This is the single most common arithmetic bug in Java, and it is quiet: an average that comes out as a whole number looks plausible enough to ship. The fix is to cast one operand before dividing.

%, the remainder operator, is more useful than it looks — it is how you test divisibility and how you wrap a value into a range:

int n = 14;
System.out.println(n % 2 == 0);        // true — even
System.out.println(n % 10);            // 4 — last digit
System.out.println((n + 3) % 7);       // 3 — wrap into 0..6, e.g. days of the week

Increment and decrement

int i = 5;
i++;                       // 6
i--;                       // 5

int x = 5;
System.out.println(x++);   // prints 5, THEN x becomes 6  (post-increment)

int y = 5;
System.out.println(++y);   // y becomes 6, THEN prints 6  (pre-increment)

On its own line they are identical, and that is where you should keep them. The prefix/postfix distinction only matters when you use the value in the same expression — which is a thing you can simply not do. Code like arr[i++] = i++; is legal, confusing, and has no place in anything you want to maintain.

Comparison, and the == trap

int a = 5, b = 8;
System.out.println(a < b);     // true
System.out.println(a >= b);    // false
System.out.println(a != b);    // true

Trap 2: == on objects compares identity, not contents. For primitives it compares values, which is what you want. For anything else it asks "are these the very same object in memory", which is almost never what you want:

String one = "hello";
String two = "hello";
String three = new String("hello");

System.out.println(one == two);          // true  — both point at the same pooled literal
System.out.println(one == three);        // false — new String() made a second object
System.out.println(one.equals(three));   // true  — same characters. This is the right question.

The true on that first line is what makes this dangerous: == appears to work on strings during testing and then fails on a string that came from a file, a database or a web request. The rule is unconditional — compare objects with equals, primitives with ==.

The one place == is right for an object is checking for null, since null is not an object and has no equals to call.

Logical operators and short-circuiting

boolean loggedIn = true, admin = false;

System.out.println(loggedIn && admin);   // false — AND, both must be true
System.out.println(loggedIn || admin);   // true  — OR, either will do
System.out.println(!admin);              // true  — NOT

Trap 3 — except this one is a feature. && and || short-circuit: if the left side settles the answer, the right side is never evaluated. You should rely on this deliberately, because it is how you guard against null in a single expression:

String name = null;

// Safe: if name is null, the left side is false and length() is never called
if (name != null && name.length() > 3) {
    System.out.println("long name");
}

// The single & does NOT short-circuit — it evaluates both sides and throws
// if (name != null & name.length() > 3) { }   // NullPointerException

Order matters, and it is not stylistic: the null check must come first. Swap the two halves and the guard does nothing.

Assignment and the compound operators

int total = 10;
total += 5;     // 15   same as total = total + 5
total -= 3;     // 12
total *= 2;     // 24
total /= 4;     // 6

String s = "a";
s += "b";       // "ab" — works on Strings too

The compound operators quietly include a cast, which is occasionally a trap of its own:

int n = 10;
// n = n * 1.5;     // does not compile: double cannot be converted to int
n *= 1.5;           // compiles, gives 15 — the hidden cast truncates

The ternary operator

condition ? valueIfTrue : valueIfFalse — an if that produces a value rather than executing a branch:

int age = 20;
String status = age >= 18 ? "adult" : "minor";

// Genuinely useful for defaults
String input = null;
String display = input != null ? input : "unknown";

It is at its best assigning one variable, and at its worst nested. If you find yourself writing a ? b : c ? d : e, use an if — or a switch, which Conditional Statements covers.

Precedence

The rules follow arithmetic, extended: multiplicative before additive, comparison before logical, assignment last. && binds tighter than ||.

System.out.println(2 + 3 * 4);              // 14, not 20

boolean a = true, b = false, c = true;
System.out.println(a || b && c);           // true  — reads as a || (b && c)
System.out.println((a || b) && c);         // true  — but say which one you meant

You do not need to memorise the full precedence table. You need to know that one exists, and to add parentheses whenever a reader would otherwise have to consult it. Explicit parentheses cost nothing and remove an entire class of argument.

Next

Operators produce values, and the type you will combine them with most is String. String is next — why it is immutable, why == lies, and when + in a loop becomes a performance problem.