Control Flow
Booleans & Logic
Java's boolean is its own type, with exactly two values and no truthiness. A condition must be a real boolean, so boolean b = 1 and if (1) won't compile, and the compiler catches the =/== slip. Covers comparisons, the logical operators &&, || and !, short-circuit evaluation as a guard, precedence, and the floating-point equality trap. Every example is compiled and run.
Suggest an editBooleans & Logic — Yes/No as a Real Type
Every decision in a program comes down to a yes-or-no question, and Java gives those questions their own type: boolean, with exactly two values, true and false.
- Nothing else counts as a yes or no.
1isn't "true",0isn't "false", and the compiler rejects code that mixes them up. This trips up newcomers from C or Python, where numbers double as conditions. - You build conditions from comparisons (
<,==, …) and combine them with the logical operators&&,||and!. &&and||short-circuit: they stop the moment the answer is known.
💡 The core idea.
booleanis its own type with exactly two values,trueandfalse.- Nothing else counts as a yes or no —
1and0are not conditions. - You build conditions from comparisons, combine them with
&&,||,!, and lean on short-circuit evaluation.
This builds on the primitive types — boolean is one of the eight — and the comparison of == vs .equals from strings. Every output below was produced by compiling and running the code.
You'll be able to: predict the value of a comparison or a &&/||/! expression, a negated compound one included; explain why boolean b = 1 and 1 < x < 10 do not compile, and fix each; put a guard on the left of &&, and predict when the right side never runs; add the parentheses a condition needs where !, + and a comparison meet; compare two double values with a tolerance instead of ==.
📘 How to read the Intuition boxes. Each one is built in three moves:
- The mechanism — what the compiler and the JVM do.
- A concrete bite — a specific, runnable failure (often a real compiler error), shown so the trap is visible.
- The earned rule — the decision heuristic, now justified rather than asserted, plus its cost.
Table of contents
- The
booleantype: no truthiness - Comparisons produce booleans
- The logical operators
&&,||,! - Short-circuit evaluation
- Precedence and a floating-point caveat
- Mental-model summary
- Gotcha checklist
- Check yourself
- Sources
1. The boolean type: no truthiness
A boolean holds exactly one of two values, written true and false [1]. You write them in lowercase, without quotes.
They're boolean literals: values written straight into the code, like 42 or 'A'. They aren't text, and they aren't keywords either [2]. And that's the whole type.
Output:
true
falseAnalysis. Two boolean variables, two values, printed as true and false. There's no third option and no numeric stand-in. A boolean is only ever true or false.
Intuition.
Mechanism. boolean is a separate type with no link to the integers. C and Python treat zero as false and any other number as true. Java has no conversion from a number to a boolean at all.
Concrete bite. So assigning a number to a boolean doesn't mean "0 is false, 1 is true". It doesn't compile:
Compiler error:
Main.java:3: error: incompatible types: int cannot be converted to boolean
boolean b = 1;
^
1 error1 is an int, b is a boolean, and Java has no rule that turns one into the other. The same strictness will reject if (count) when you meet if in the next chapter. A condition must be a boolean, never a number.
Comparing a boolean with a number fails the same way, with a message of its own:
Compiler error:
Main.java:4: error: incomparable types: boolean and int
System.out.println(done == 1);
^
1 error💡 Earned rule. Conditions in Java are genuine booleans. To ask "is this non-zero?", write the comparison: count != 0 [1].
It costs you a few more characters than the bare count. The payoff: a whole family of C bugs, where a stray integer gets read as a truth value, simply can't happen. The compiler forbids the mix-up.
2. Comparisons produce booleans
You'll rarely write true/false literals. Instead, you compute booleans by comparing values. Java has six comparison operators, and each one takes two values and produces a boolean:
<,>,<=and>=compare sizes;==asks "equal?", and!=asks "not equal?".
Output:
true
true
false
trueAnalysis. 3 < 5 is true, 3 == 3 is true, 3 != 3 is false. The last pair shows the useful shape: age >= 18 compares, then stores the resulting boolean in adult, ready for a later decision.
On primitives like these, == compares values. On objects, it compares identity, as Strings, the Basics showed.
When you compare two different numeric types, Java promotes them first, just the way arithmetic does [3]. A char compares as its number:
Output:
true
true
true5 became 5.0 before the test, so the two are equal. 'a' is the number 97, and 'b' is 98.
Intuition.
Mechanism. A comparison is an expression whose result type is boolean. == (two equals signs) is the question "are these equal?", while = (one equals sign) is the action "assign".
- A comparison produces a
boolean. - An assignment produces the value it assigned [4].
Concrete bite. C accepts if (x = 5), at most with a warning, and treats the 5 as true. It's a classic silent bug. Java catches the slip at compile time, because this assignment's result is an int, not a boolean:
Compiler error:
Main.java:4: error: incompatible types: int cannot be converted to boolean
boolean ok = (x = 5);
^
1 error(x = 5) assigns 5 to x and evaluates to the int 5, and storing that in a boolean fails to compile. The very typo that compiles and misbehaves in C is a compile error here.
Non-example: the slip the compiler can't see. The protection comes from the types. When the variable is itself a boolean, the assignment produces a boolean, so the typo compiles:
Output:
true
trueThe line meant to ask "is done true?". Instead it set done to true, and that's the only reason the answer came out true.
💡 Earned rule. Use == to compare and = to assign. With numbers, the compiler stops the slip wherever a boolean is required.
With a boolean variable it can't, so never compare one with true or false. Use the variable itself: done, or !done for "not done". Then there's no == to mistype.
3. The logical operators &&, ||, !
To combine yes/no answers, use the three logical operators:
&&("and") is true only if both sides are true;||("or") is true if either side is true;!("not") flips aboolean.
Output:
false
true
falseAnalysis. a && b is false because b is false, and "and" needs both. a || b is true because a is true, and "or" needs only one. !a flips true to false.
These three build every compound condition you'll ever write. Here are all four cases:
a |
b |
a && b |
a || b |
|---|---|---|---|
true |
true |
true |
true |
true |
false |
false |
true |
false |
true |
false |
true |
false |
false |
false |
false |
Non-example: a chained comparison. In maths, you write "x is between 1 and 10" as 1 < x < 10. Java rejects that:
Compiler error:
Main.java:4: error: bad operand types for binary operator '<'
System.out.println(1 < x < 10);
^
first type: boolean
second type: int
1 errorJava reads 1 < x < 10 as (1 < x) < 10 [5]. The first < gives a boolean, and the second < then compares that boolean with 10. The message names the two types it got: boolean and int. A range takes two comparisons joined by &&:
Output:
true
falseIntuition.
Mechanism. Each operator combines booleans into a boolean. ! binds tightest, then &&, then ||. So ! applies only to the term it touches, not to a whole expression.
Concrete bite. The precedence of ! is where intuition lets you down. "Not (a and b)" is not the same as "not-a and not-b":
Output:
true
false!(true && false) is !(false) = true. But !true && !false is false && true = false. Negating a compound condition flips and to or, and or to and. This is De Morgan's law:
!(a && b)equals!a || !b;!(a || b)equals!a && !b.
A working-age check shows the second law in action. You can write "not a child and not retired" either way:
Output:
false false
true trueThe two forms agree for both ages. The || became &&, and each comparison flipped: < to >=, and >= to <.
💡 Earned rule. Build conditions from &&, || and !. Write a range as two comparisons joined by &&. When you negate a compound test, De Morgan applies: !(a && b) means !a || !b.
Forget De Morgan, and you get a condition that looks negated but tests the wrong thing. When in doubt, wrap the whole expression in !( … ) instead of distributing the ! by hand.
4. Short-circuit evaluation
&& and || are short-circuiting: they evaluate the left side first and stop as soon as they know the answer [6] [7].
false && anythingisfalsewithout ever looking at the right side.true || anythingistruethe same way.
This isn't just about speed. It's a guard.
Output:
falseAnalysis. x is 0, so x != 0 is false, and && stops there. The right side, 10 / x, is never evaluated, so the divide-by-zero never happens. The whole expression is false, and the program runs cleanly. The first test guarded the second.
Intuition.
Mechanism. && computes its left operand, and only when that's true does it compute the right. || is the mirror image: it computes the right only when the left is false. The single-character cousins & and | do not short-circuit: they always evaluate both sides [6].
Concrete bite. Swap && for & and the guard is gone. Both sides run, and 10 / 0 throws:
Output (a thrown exception):
Exception in thread "main" java.lang.ArithmeticException: / by zero& evaluated 10 / x even though x != 0 was already false, and dividing by zero threw ArithmeticException. Same logic, one missing character, and you get a crash instead of a clean false.
When the right side is skipped, everything in it is skipped, updates included. Here ++n sits on the right of each operator:
Output:
false 0
false 1With &&, n > 5 was false, so ++n never ran and n stayed 0. With &, both sides ran, and n became 1. The results agree, but the program's state doesn't.
The third non-short-circuit operator is ^, "exclusive or", which is true when the two sides differ [8]. On booleans it gives the same answer as !=:
Output:
true
false
true💡 Earned rule. Use && and || (not &/|) for conditions, and put the cheap, protective test first: x != 0 && 10 / x > 0. Keep updates such as ++n out of conditions, so whether they run never depends on the left side.
The catch is that order now matters. Short-circuiting protects the right side only when the guard is on the left. Get the order wrong, and the condition loses its protection and can still crash.
(The same guard protects text: s != null && s.length() > 0. null arrives properly in References, Equality & the Object Model.)
5. Precedence and a floating-point caveat
When && and || mix, && binds tighter than ||, so a || b && c means a || (b && c). One comparison also deserves an early warning: == on floating-point numbers is treacherous, because Java stores decimals only approximately.
Output:
true
falseAnalysis. true || false && false groups as true || (false && false) = true || false = true, because && binds tighter. Force the other grouping with parentheses, and you get (true || false) && false = true && false = false. The parentheses changed the answer.
The full order, tightest first, for the operators you know so far [9]:
| Level | Operators |
|---|---|
| 1 (tightest) | !, unary -, ++, -- |
| 2 | *, /, % |
| 3 | +, - (and + joining text) |
| 4 | <, >, <=, >= |
| 5 | ==, != |
| 6 | &, then ^, then | |
| 7 | && |
| 8 | || |
| 9 (loosest) | =, +=, and the other assignments |
Intuition.
Mechanism. Precedence is fixed grammar. The comparisons bind tighter than && and ||, so a < b && c < d already means (a < b) && (c < d). ! sits on the top level, and + binds tighter than any comparison.
Concrete bite. Two everyday lines run straight into those last two rules. Both look fine, and neither compiles:
Compiler error:
Main.java:4: error: bad operand type int for unary operator '!'
System.out.println(!age >= 18);
^
1 error! grabbed age before >= ran, and ! works only on a boolean [10]. Write !(age >= 18), or better, age < 18.
Compiler error:
Main.java:5: error: bad operand types for binary operator '>='
System.out.println("adult: " + age >= 18);
^
first type: String
second type: int
1 error+ ran first and built the text "adult: 20". Then >= tried to compare that String with 18. Line 4, with its parentheses, is the fix, and it would print adult: true.
A floating-point caveat. Numbers & Arithmetic showed that 0.1 + 0.2 isn't exactly 0.3. A == test turns that tiny error into a wrong answer:
Output:
false
0.300000000000000040.1 + 0.2 is 0.30000000000000004, not 0.3, so == 0.3 is false. Nothing is broken. double stores approximations, and == compares them exactly. The fix is to ask "are these close?" instead:
Output:
false
trueMath.abs(sum - 0.3) is the size of the difference. It's far below 1e-9 (0.000000001), so the two count as equal. Pick a tolerance that suits the size of your values. Near 1e9, neighbouring doubles sit about 1.2e-7 apart, so 1e-9 would be too strict there [13].
One double isn't even equal to itself: NaN, the "not a number" that 0.0 / 0 produces. The rule is fixed, and == with NaN is always false [11].
Output:
false
trueTo test for NaN, call Double.isNaN [12].
💡 Earned rule. Parenthesise whenever && and || mix, whenever ! negates a comparison, and whenever a comparison sits inside a + chain. Never compare doubles with ==. Test that the difference is within a small tolerance (Math.abs(a - b) < 1e-9) instead.
Trust == on floating-point, and you get a comparison that's false for values you consider equal, with no error to flag it. It's the same "silent wrong answer" hazard as integer overflow, in a different disguise.
6. Mental-model summary
| Principle | Consequence |
|---|---|
boolean is its own type; there is no truthiness |
boolean b = 1 and done == 1 won't compile; conditions must be real booleans |
Comparisons produce a boolean; == asks, = assigns |
boolean ok = (x = 5) is a compile error, but (done = true) on a boolean compiles, so use done itself |
| Comparisons do not chain | 1 < x < 10 won't compile; write 1 < x && x < 10 |
&&/` |
|
&&/` |
|
! and + bind tighter than comparisons; && tighter than ` |
|
| Floats are approximate, and NaN equals nothing | Compare doubles with a tolerance; test NaN with Double.isNaN |
7. Gotcha checklist
| Symptom | Likely cause | Fix |
|---|---|---|
incompatible types: int cannot be converted to boolean |
a number where a condition belongs (boolean b = 1), or = written for == |
write a comparison: count != 0, x == 5 |
incomparable types: boolean and int |
a boolean compared with a number (done == 1) |
use the boolean itself: done |
A boolean variable changed when you only meant to test it |
done = true where you meant done == true; it compiles |
test done or !done, never == true |
bad operand types for binary operator '<', with first type: boolean |
a chained comparison, 1 < x < 10 |
1 < x && x < 10 |
bad operand type int for unary operator '!' |
!age >= 18: ! reached the number first |
!(age >= 18), or age < 18 |
bad operand types for binary operator '>=', with first type: String |
"adult: " + age >= 18: + joined the text first |
"adult: " + (age >= 18) |
| A negated compound condition tests the wrong thing | the ! was distributed without flipping && and || |
De Morgan: !(a && b) is !a || !b; or keep the whole !( … ) |
ArithmeticException: / by zero despite a guard |
&/| in place of &&/||, or the guard on the right |
use &&/|| with the guard first |
| A counter in a condition did not change | its ++ sat on a right side that short-circuiting skipped |
update the variable on its own line |
A mixed &&/|| condition groups unexpectedly |
&& binds tighter than || |
add parentheses |
| Two equal-looking decimals compare as unequal | floating-point approximation | Math.abs(a - b) < 1e-9 |
x == x is false |
x is NaN |
Double.isNaN(x) |
✅ Check yourself
One check per objective. Answer before you open anything.
Why is 0.1 + 0.2 == 0.3 false? Write a test that answers true for those two values.
A double stores the nearest binary fraction, not the exact decimal. 0.1 + 0.2 lands on 0.30000000000000004, and == compares exactly. Ask whether the difference is tiny, instead:
Math.abs((0.1 + 0.2) - 0.3) < 1e-9 is true.
The 🧪 box below: what do its lines print, and what changes with &?
5 > 3 prints true. 5 > 3 && 2 > 4 prints false. 5 > 3 || 2 > 4 prints true. !(5 > 3) prints false.
With int n = 0;, n != 0 && 100 / n > 0 prints false: the left side is false, so && never divides. With &, both sides run, and 100 / 0 throws java.lang.ArithmeticException: / by zero.
📚 Sources
- The Java Language Specification, Java SE 25, §4.2.5 "The
booleanType andbooleanValues" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-4.html#jls-4.2.5 - The Java Language Specification, Java SE 25, §3.9 "Keywords" ("
trueandfalseare not keywords"; they are boolean literals) — https://docs.oracle.com/javase/specs/jls/se25/html/jls-3.html#jls-3.9 - The Java Language Specification, Java SE 25, §15.20.1 "Numerical Comparison Operators" and §15.21.1 "Numerical Equality Operators" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.20.1
- The Java Language Specification, Java SE 25, §15.26 "Assignment Operators" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.26
- The Java Language Specification, Java SE 25, §15.20 "Relational Operators" (
a<b<c"is always a compile-time error") — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.20 - The Java Language Specification, Java SE 25, §15.23 "Conditional-And Operator
&&" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.23 - The Java Language Specification, Java SE 25, §15.24 "Conditional-Or Operator
||" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.24 - The Java Language Specification, Java SE 25, §15.22.2 "Boolean Logical Operators
&,^, and|" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.22.2 - The Java Language Specification, Java SE 25, Chapter 15 "Expressions" ("Precedence among operators is managed by a hierarchy of grammar productions") — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html
- The Java Language Specification, Java SE 25, §15.15.6 "Logical Complement Operator
!" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.15.6 - The Java Language Specification, Java SE 25, §15.21.1 "Numerical Equality Operators
==and!=" ("If either operand is NaN, then the result of==is false") — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.21.1 - Java SE 25 API,
java.lang.Double.isNaN— https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/Double.html#isNaN(double) - Java SE 25 API,
java.lang.Math.ulp(the distance to the nextdouble) — https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/Math.html#ulp(double)
🧪 Predict, then check. Predict each line's output before running:
System.out.println(5 > 3);System.out.println(5 > 3 && 2 > 4);System.out.println(5 > 3 || 2 > 4);System.out.println(!(5 > 3));
Then a harder one: with int n = 0;, what does System.out.println(n != 0 && 100 / n > 0); print? What would change if you wrote & instead of &&? Explain that second answer in terms of short-circuiting before you run it.
Your Turn
Before you move on, check your understanding with the coach — explain the idea, apply it, weigh the trade-offs, then defend your reasoning.