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 edit

Booleans & 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. 1 isn't "true", 0 isn'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.

  • boolean is its own type with exactly two values, true and false.
  • Nothing else counts as a yes or no — 1 and 0 are 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:

  1. The mechanism — what the compiler and the JVM do.
  2. A concrete bite — a specific, runnable failure (often a real compiler error), shown so the trap is visible.
  3. The earned rule — the decision heuristic, now justified rather than asserted, plus its cost.

Table of contents

  1. The boolean type: no truthiness
  2. Comparisons produce booleans
  3. The logical operators &&, ||, !
  4. Short-circuit evaluation
  5. Precedence and a floating-point caveat
  6. Mental-model summary
  7. Gotcha checklist
  8. Check yourself
  9. 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
false

Analysis. 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 error

1 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
true

Analysis. 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
true

5 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
true

The 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 a boolean.

Output:

false
true
false

Analysis. 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 error

Java 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
false

Intuition. 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 true

The 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 && anything is false without ever looking at the right side.
  • true || anything is true the same way.

This isn't just about speed. It's a guard.

Output:

false

Analysis. 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 1

With &&, 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
false

Analysis. 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.30000000000000004

0.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
true

Math.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
true

To 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

  1. The Java Language Specification, Java SE 25, §4.2.5 "The boolean Type and boolean Values" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-4.html#jls-4.2.5
  2. The Java Language Specification, Java SE 25, §3.9 "Keywords" ("true and false are not keywords"; they are boolean literals) — https://docs.oracle.com/javase/specs/jls/se25/html/jls-3.html#jls-3.9
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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)
  13. Java SE 25 API, java.lang.Math.ulp (the distance to the next double) — 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.

Mark as readBook versionfd26460