Topic 27 of 119 3. Operators in Java Interactive Tutorial

Logical Operators in Java: Combining Conditions With &&, ||, and !

12 mins read Last updated: 22 Aug 2026 19,671 views

Direct Answer & Core Concept

Java has three logical operators, all of which work on `boolean` values only: logical AND `&&`, logical OR `||`, and logical NOT `!`. The defining behaviour of `&&` and `||` is short-circuit evaluation, which means the right-hand operand is evaluated only if it can still affect the result. For `&&`, once the left operand is false the answer must be false, so the right side is skipped. For `||`, once the left operand is true the answer must be true, so the right side is skipped. That makes the guard-clause pattern safe, because `text != null && text.length() > 0` never calls `length()` on a null reference, whereas the non-short-circuiting `&` evaluates both sides and throws a NullPointerException. The operators bind in a fixed order, with `!` strongest, then `&&`, then `||`, which is why `true || false && false` is true rather than false. Negation is not the same as bitwise complement: `!x` inverts a boolean and rejects an int, while `~x` flips the bits of a numeric value. De Morgan's laws give two safe rewrites, `!(a && b)` becomes `!a || !b` and `!(a || b)` becomes `!a && !b`, and pushing a negated test down to its component parts often produces cleaner, cheaper code than testing a whole negated condition.

Detailed Explanation & Mechanics

1. The Three Logical Operators

Java's logical operators take boolean operands and produce a boolean. There are exactly three, and unlike the bitwise operators from the previous topic, none of them accept numbers.

OperatorNameOperandsMeaning
&&Logical ANDTwo booleansTrue only when both are true
||Logical ORTwo booleansTrue when at least one is true
!Logical NOTOne booleanTrue when the operand is false
Main.java
Run Online
boolean a = true;
boolean b = false;
System.out.println(a && b);
// false
System.out.println(a || b);
// true
System.out.println(!a);
// false

Two compile errors are worth internalising, because they are the clearest way to see the difference from the bitwise family.

Main.java
Run Online
boolean p = true;
boolean q = true;
System.out.println(p ^ q);
// false, legal, this is the bitwise XOR on booleans
System.out.println(p & q);
// true,  legal, the bitwise AND on booleans
System.out.println(!5);
// error: bad operand type int for unary operator '!'
System.out.println(~5);
// -6,  legal, this is bitwise complement instead
System.out.println(p && 5);
// error: bad operand types for binary operator '&&'

! negates truth, not bits. To flip the bits of a number you want ~, which is the subject of the unary operators topic.

2. The Truth Tables

The truth tables are identical to the bitwise operators, which is not a coincidence since & and && agree on booleans. What differs is whether the second operand is evaluated at all.

aba && ba || b
falsefalsefalsefalse
falsetruefalsetrue
truefalsefalsetrue
truetruetruetrue

The useful way to read the tables is as an early exit. For &&, a false left operand settles the answer, and for ||, a true left operand settles it. One row in each table is the interesting one, and those two rows are exactly where the right-hand side gets skipped.

Main.java
Run Online
a b | a && b reads as | a || b reads as ---- ---- + ---------- ---------------- + ---------- ---------------- F F | false b not needed | false both needed F T | false b SKIPPED | true b not needed T F | false both needed | true b SKIPPED T T | true both needed | true b not needed

3. Short-Circuit Evaluation

Short-circuiting means the right-hand operand is evaluated only when the left one has not already determined the answer. It is not an optimisation bolted on afterwards; it is the defined behaviour of the operator.

To see it, give each operand a visible side effect.

Main.java
Run Online
static boolean t(String name) {
    System.out.println("  evaluated " + name);
    return true;
}static boolean f(String name) {
    System.out.println("  evaluated " + name);
    return false;
}if (t("a") || t("b") || t("c")) {
    // only "a" is ever printed
}if (f("a") && f("b") && f("c")) {
    // only "a" is ever printed
}
Main.java
Run Online
t("a") || t("b") || t("c") "a" is true, so the whole expression is already true and b and c are never called evaluated a f("a") && f("b") && f("c") "a" is false, so the whole expression is already false and b and c are never called evaluated a

A counter makes it unambiguous.

Main.java
Run Online
int[] calls = {
    0
};
boolean r1 = false && bump(calls);
// false
boolean r2 = true || bump(calls);
// true
System.out.println(calls[0]);
// 0, bump never ran at all

Note that both chains stop after the first operand. && and || are short-circuiting all the way down, so in a longer chain like a || b || c you may evaluate only one or two of the three.

4. Short-Circuiting Is What Makes Null Guards Safe

The reason short-circuiting matters far beyond performance is that it turns a sequence of conditions into a safety mechanism. The right-hand side only runs when it is safe to run.

Main.java
Run Online
String text = null;
if (text != null && text.length() > 0) {
    System.out.println("Text is valid");
} else {
    System.out.println("Text is null or empty, and no exception was thrown");
}

The output is the else branch, and nothing blows up. Swap && for the non-short-circuiting & and the same code throws.

Main.java
Run Online
text != null && text.length() > 0 text != null is false -> answer is already false text.length() is never called, so no NullPointerException text != null & text.length() > 0 text != null is false, but & evaluates BOTH sides regardless text.length() is called on null -> NullPointerException
Main.java
Run Online
try {
    if (text != null & text.length() > 0) {
        // never reached
    }
} catch (NullPointerException e) {
    System.out.println(e.getMessage());
}

The message is Cannot invoke "String.length()" because "" is null. That single difference between & and && is the whole reason the guard idiom exists.

The same pattern works with ||, where the guard simply inverts.

Main.java
Run Online
if (text == null || text.isEmpty()) {
    System.out.println("The || form caught it, and isEmpty() was never called");
}

Both forms work. The && version says proceed only if valid, while the || version says bail out if invalid. Pick whichever reads better, and put the risky condition on the right.

5. Ordering Matters Within a Chain

Because evaluation stops early, the order of the conditions inside a single chain is not cosmetic. Put the cheap and safest test first, since a wrong order either throws or does work you did not need.

Main.java
Run Online
// cheap first: the array length is a field read
if (items.length > 0 && items[0].getName().isEmpty()) {
    ...
}// risky first: dereferences items[0] before checking there is anything at index 0
if (items[0].getName().isEmpty() && items.length > 0) {
    ...
}

The second version throws ArrayIndexOutOfBoundsException on an empty array, because the left operand is evaluated before the length is ever consulted. The same applies to method calls: put the cheap boolean field before the expensive database call.

Main.java
Run Online
guard clause ordering always put the cheap, side effect free test first always put the dereference, the network call or the allocation second cost of a wrong order is not just performance, it is a crash

6. Precedence: ! Then && Then ||

The three operators have a fixed binding order, strongest first: ! binds tighter than &&, which binds tighter than ||. This is the single most common source of a condition that does not do what it looks like.

OperatorPrecedenceAssociativity
!HighestRight to left
&&MiddleLeft to right
||Lowest of the threeLeft to right
?:Below all threeRight to left
Main.java
Run Online
System.out.println(true || false && false);
// true
System.out.println((true || false) && false);
// false

The first line is true because && is evaluated first, giving true || (false && false), which is true. Written the other way round it is false. They look similar and mean opposite things, which is exactly the trap.

! binding tighter than && is the second half of the same trap.

Main.java
Run Online
System.out.println(!false && true);
// true,  parsed as (!false) && true
System.out.println(!(false && true));
// true,  same answer by coincidence

Whenever the grouping is not obvious, write the parentheses. The compiler does not warn you, and neither does the reader.

7. De Morgan's Laws

De Morgan's laws let you push a negation down through a compound condition. There are two, and both hold in Java.

OriginalEquivalent
!(a && b)!a || !b
!(a || b)!a && !b
Main.java
Run Online
boolean a = true;
boolean b = false;
System.out.println(!(a && b));
// true
System.out.println(!a || !b);
// true
System.out.println(!(a || b));
// false
System.out.println(!a && !b);
// false

Verified across all four input combinations, both forms give the same answer every time.

Main.java
Run Online
for (boolean p : new boolean[] {
    false, true
}) {
    for (boolean q : new boolean[] {
        false, true
    }) {
        System.out.println(p + " " + q + " -> " +(!(p && q)) + " and " +(!p || !q));
    }
}

The reason to care is readability. A negated compound condition is hard to follow, and the usual fix in Java is to pull the negation inward until each test is a plain positive check.

Main.java
Run Online
// harder to read
if (!(user != null && user.isActive())) {
    return;
}// easier, and De Morgan has been applied for you
if (user == null || !user.isActive()) {
    return;
}

The same rewrite is what makes it safe to move the null test into the negation, since !(a && b) becomes !a || !b and only then is it obvious that b is guarded by a.

8. The Non-Short-Circuit Forms: & and |

Java reuses the bitwise operators on booleans, and on booleans they produce the same truth value. The single difference is that & and | always evaluate both operands, which Topic 26 covered from the bitwise side.

ExpressionOperandsRight side evaluated
a && bboolean onlyOnly if needed
a || bboolean onlyOnly if needed
a & bboolean or integralAlways
a | bboolean or integralAlways
a && b on intsCompile errorNot applicable

There are two situations where the non-short-circuiting form is genuinely the right choice, and they are both about evaluating both sides for effect rather than for value.

Main.java
Run Online
boolean a = f();
// f prints as it runs
boolean b = f();
boolean r1 = a && b;
// if a is false, b still runs here, because f() already assigned it
boolean r2 = a & b;
// both are forced into an expression, so the intent is explicit

The second is a readability choice when you want to signal that evaluation order does not matter, which is uncommon. Outside of that, use && and || for every ordinary condition.

9. Guard Clauses and the Early Return

The most valuable use of the short-circuit behaviour is the guard clause, which inverts a nested if into a flat test at the top of the method. Deeply nested conditions are where logic bugs hide, and the guard form removes a whole indentation level plus one branch.

Main.java
Run Online
// nested, and every extra level is a chance to get the logic wrong
if (user != null) {
    if (user.isActive()) {
        if (user.hasPermission("edit")) {
            doEdit();
        }
    }
}// flat, and the same three conditions
if (user == null) return;
if (!user.isActive()) return;
if (!user.hasPermission("edit")) return;
doEdit();

The flattened version also removes the "arrow anti-pattern", where a long line of repeated returns walks off the right edge of the screen. And because each guard can use &&, the checks can be combined when it improves readability.

Main.java
Run Online
if (user == null || !user.isActive() || !user.hasPermission("edit")) {
    return;
}doEdit();

Both forms are correct. Combine them when the tests are related and short-circuiting makes the combination safe, and split them onto separate lines when each one deserves its own comment or its own error message.

10. Conditional Operator Interaction

The ternary operator ?: has the lowest precedence of the three logical operators, so a logical expression as its condition needs no parentheses. That is worth confirming rather than assuming.

Main.java
Run Online
System.out.println(true || false ? 1 : 2);
// 1

That is 1 because the condition parsed as (true || false) ? 1 : 2, which is true, so it yields 1. Adding the parentheses makes the grouping explicit and costs nothing.

Main.java
Run Online
int a = 5, b = 3;
int c = 9;
System.out.println(a > b ? a : b);
// 5
System.out.println((a > b ?(a > c ? a : c) :(b > c ? b : c)));
// 9, nested

Nesting a ternary inside the branches is legal and each nested one acts as the condition of the next, so a three-way maximum needs no if statement. Resist it beyond two levels though, because a nested ternary is harder to read than the equivalent if and the compiler will not help.

11. Pattern Matching With instanceof Guards

From Java 16 you can bind a value in the instanceof test itself, and that is where short-circuiting becomes essential rather than merely useful. Without &&, the bound variable would not exist yet in the next condition.

Main.java
Run Online
Object o = "hello";
if (o instanceof String s && s.length() > 0) {
    System.out.println(s.length());
    // 5
}

If o were an Integer, instanceof String would be false, the && would stop, and s.length() would never be evaluated on a variable that does not exist. Writing s.length() > 0 without the guard does not compile, because s is only in scope in the true branch of the test.

Main.java
Run Online
Object p = 42;
if (p instanceof Integer n && n > 10) {
    System.out.println("n > 10 : " + n);
} else {
    System.out.println("the condition was false, so the else branch runs");
}

Note that with n bound to 42 the test n > 10 is true, so the first branch runs. Swap in a value of 5 and the guard still protects you, and the else runs instead. The pattern is always safe regardless of the runtime type.

12. Common Mistakes

MistakeConsequence
a == b && c style precedence slipsCondition silently means something else
Writing & or | for a conditionBoth sides always run, null guard throws
Testing a.length() == 0 && b before checking lengthArrayIndexOutOfBoundsException
!(a && b) left unflattenedHard to read, guard is less obvious
Confusing ! with ~! rejects numbers, ~ flips bits
Using != to compare stringsWrong unless interned, use equals

The string comparison trap is the one that catches even experienced Java developers, because == on two objects compares references, not contents.

Main.java
Run Online
Integer i1 = 100, i2 = 100;
System.out.println(i1 == i2);
// true, both come from the Integer cache
Integer i3 = 1000, i4 = 1000;
System.out.println(i3 == i4);
// false, outside the cache range
System.out.println(i3.equals(i4));
// true, the correct comparison

The wrapper cache covers -128 to 127, so small values appear to work by accident. And the XOR trick from the bitwise topic does not rescue you here, because XOR compares the numeric values and ignores the distinction between a cached and a fresh object entirely.

Main.java
Run Online
System.out.println((i3 ^ i4) == 0);
// true, even though i3 == i4 is false

13. Logical Operators Cheat Sheet

QuestionAnswer
Logical operators&& || !
Valid operand typesboolean only
true && falsefalse
true || falsetrue
!truefalse
Short circuit ANDRight side skipped when the left is false
Short circuit ORRight side skipped when the left is true
&& versus &Logical skips, bitwise always evaluates both
|| versus |Logical skips, bitwise always evaluates both
Precedence, strongest first!, then &&, then ||
true || false && falsetrue, because && binds first
Null guardobj != null && obj.method()
De Morgan!(a && b) is !a || !b
!(a || b) equals!a && !b
!5Compile error, bad operand type int
~5-6, bitwise complement not negation
a ^ b on booleansLegal, exclusive or
Pattern guardo instanceof String s && s.isEmpty()
Compare Integer contentsUse equals, == compares references
Ternary precedenceLowest, so no parentheses needed on its condition

Short-circuiting is one of the highest value ideas in the language, because it is not just an optimisation. It is the mechanism that lets you write a cheap, safe check in front of an expensive, dangerous one, which is why guard clauses read so well. Master the null guard, keep the cheap test on the left, and remember that && binds tighter than ||.

Runnable Code Example: Logical Operators in Java

Java 17+
Main.java
Run Online
public class LogicalDemo {
    public static void main(String[] args) {
        // ---------- the three operators ----------
        boolean a = true;
        boolean b = false;
        System.out.println("a && b = " +(a && b));
        System.out.println("a || b = " +(a || b));
        System.out.println("!a     = " +(!a));
        // ---------- short circuiting: the right side is skipped ----------
        String text = null;
        // safe, because text.length() is never reached when text is null
        if (text != null && text.length() > 0) {
            System.out.println("Text is valid");
        } else {
            System.out.println("Text is null or empty, and no exception was thrown");
        } // ---------- the guard works with || as well ----------
        if (text == null || text.isEmpty()) {
            System.out.println("The || form caught it, and isEmpty() was never called");
        } // ---------- precedence: && binds tighter than || ----------
        System.out.println("true || false && false = " +(true || false && false));
        System.out.println("which is the same as    " +((true || false) && false));
        // ---------- ! is stronger than both ----------
        System.out.println("!false && true = " +(!false && true));
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1

What is short-circuit evaluation and why does it matter?

It means && and || evaluate the right-hand operand only when the left one has not already determined the result. For && a false left operand settles the answer, and for || a true left operand settles it, so the right side is skipped. It matters because it lets you put a cheap, safe test in front of an expensive or dangerous one, which is what makes text != null && text.length() > 0 safe instead of a NullPointerException.
Q2

Why does text != null && text.length() > 0 not throw a NullPointerException?

Because the left operand is false when text is null, so the answer of the && is already known to be false and the right operand is never evaluated. Replace && with & and both operands are always evaluated, so text.length() is called on null and the program throws. The exact message is Cannot invoke "String.length()" because "" is null.
Q3

What is the difference between && and &?

Both work on booleans and produce the same truth value, but && short-circuits while & always evaluates both operands. The same distinction applies to || against |. Only & and | accept integral operands, so using && or || on two ints is a compile error, and only they can be used for flag arithmetic.
Q4

What is the precedence of !, && and ||?

From strongest to weakest it is !, then &&, then ||, and the ternary operator ?: is weaker than all three. That means true || false && false is true, because the && is evaluated first giving true || (false && false). Written the other way round as (true || false) && false it is false, so always parenthesise when the grouping is not obvious.
Q5

Can you always reorder the conditions inside a && chain?

No. The order is part of the correctness, not just performance. If you put items[0].someMethod() before items.length > 0, the array access happens first and throws on an empty array. The rule is to put the cheap, side effect free test first and the dereference, allocation or network call second.
Q6

What are De Morgan's laws in Java?

There are two. NOT of an AND is an OR of NOTs, so !(a && b) is the same as !a || !b, and NOT of an OR is an AND of NOTs, so !(a || b) is the same as !a && !b. They hold for all four combinations of the two booleans. They matter for readability, because flattening !(user != null && user.isActive()) into user == null || !user.isActive() turns a hard-to-follow nested negation into two plain positive tests.
Q7

Why is ! different from ~ ?

! is a logical operator and works only on boolean, inverting true to false. ~ is a bitwise operator and works on the integral types, flipping every bit, so ~5 is -6. Writing !5 is a compile error reading bad operand type int for unary operator '!'. Mixing them up is easy because the two symbols look related, so remember that ! answers a truth question and ~ answers a bit question.
Q8

What is the difference between an if statement and the ternary operator?

The ternary is an expression, so it produces a value and can appear in an assignment or a method argument, while if is a statement and does not produce anything. The ternary has lower precedence than all three logical operators, so a logical expression works as its condition without parentheses. Nesting ternaries to express a three-way maximum is legal and each nested one acts as the next condition, but beyond two levels an if is more readable.
Q9

What is the benefit of guard clauses over nested if statements?

A guard clause inverts a condition, returns early, and flattens the method, which removes an indentation level and shrinks the cyclomatic complexity. It also reads top to bottom as a list of reasons the method is doing nothing. The same conditions can be combined with || or && when that is clearer, but separate lines are better when each check needs its own comment or error message.
Q10

When might you deliberately use & instead of &&?

Very rarely, and only when you want to signal that both operands must be evaluated regardless. For example if both sides are already-computed booleans and you want to document that evaluation order does not matter. It is not a way to avoid short-circuiting for performance, since modern JIT compilers reorder side effect free code anyway, and it is dangerous whenever either side can throw.
Q11

How do instanceof and short-circuiting work together?

Pattern matching for instanceof, introduced in Java 16, lets you bind the tested value to a variable in the condition itself, as in o instanceof String s. That variable only exists in the branch where the test was true, so any further condition on it has to be guarded with &&. Writing o instanceof String s && s.length() > 0 is safe, because the right side is only evaluated when s was actually bound.
Q12

Why does Integer == Integer sometimes work and sometimes not?

Because == on two objects compares references, not contents. The wrapper classes cache small values, so Integer objects created by boxing values from -128 to 127 are the same object and == returns true. Outside that range the two objects are distinct and == returns false even when the numbers match. This applies to Integer, Long, Double and the rest, so use equals for a value comparison, and do not try to work around it with the XOR trick from the bitwise topic since that compares numbers and ignores object identity entirely.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Treat && and || as a safety mechanism rather than just an optimisation, since that is what they actually are. The standard shape is a cheap, side effect free test on the left and the risky operation on the right, so obj != null && obj.isValid() can never dereference a null. Keep that ordering inside every chain, because the order carries the correctness: writing items.get(0) before items.size() > 0 throws on an empty collection, and putting an expensive database call before a cached boolean flag wastes the very work the short circuit exists to avoid. Prefer guard clauses to nested if statements, since an inverted condition with an early return flattens the method, cuts one indentation level and reads as a plain list of reasons the method is doing nothing. Combine checks with && or || only when the tests are related and the short circuiting makes the combination safe; give each its own line when it deserves its own comment or error message. Use && and || for conditions and reserve & and | for flag arithmetic, because the bitwise forms evaluate both operands and will happily throw on a null the guard was written to protect. Parenthesise whenever grouping is not obvious, remembering that ! binds tighter than &&, which binds tighter than ||, and that the compiler will not warn you when a condition quietly means something else than it looks. Push negations inward with De Morgan's laws so that tests read positively, and remember that ! is logical negation while ~ is bitwise complement: applying ! to a number is a compile error. Finally, do not lean on == for wrapper types, since Integer and the other wrappers cache only the range -128 to 127, so small comparisons appear to work by accident and larger ones silently fail; use equals when you mean value equality.
We value your privacy & transparency

OperateTools uses cookies and local storage to ensure basic tool functionality, preserve preferences, and analyze aggregated traffic. We never sell your personal data or index your document files. Learn more in our Privacy Policy.