if, else if and else

Branch on boolean conditions, order an else-if chain correctly and use guard clauses to keep request handlers flat and readable.

Syntaxif (condition) { ... } else if (otherCondition) { ... } else { ... }

if runs a block when its condition is true. else if adds another condition that is only tested when every earlier one was false, and else catches whatever is left. Java tests the conditions top to bottom and runs at most one branch: the first whose condition is true, or the else if none is.

Conditions must be boolean

Java has no truthiness. if (count), if (name) and if (user) do not compile, which is a surprise if you come from JavaScript or Python. Write the comparison you mean: count > 0, !name.isEmpty(), user != null.

Order matters

Because the first matching branch wins, put the most specific condition first. A check for total >= 100 placed above total >= 1000 means the second branch can never run, and the compiler will not warn you. Read a chain from the top and ask whether any later branch is already swallowed by an earlier one.

Guard clauses in handlers

In a request handler or service method, check the failure cases first and return early: missing token, wrong method, invalid input. What remains is the normal path with no extra nesting.

if (token == null || token.isBlank()) {
    return "401 Unauthorized";
}
if (!method.equals("GET")) {
    return "405 Method Not Allowed";
}
// the happy path continues here, one level deep

&& and || short-circuit: the right side is only evaluated when it can change the result. That is what makes header != null && header.startsWith("Bearer ") safe. The single & and | operators always evaluate both sides and will throw a NullPointerException in the same position.

Mistakes people make

  • Leaving out braces. if (x) a(); b(); runs b() every time, whatever the indentation suggests. Always use braces.
  • Comparing strings with ==. It compares references, not text; use equals.
  • Putting a semicolon after the condition: if (ok); is an empty statement and the following block always runs.

When every branch assigns the same variable, declare it without a value (String status;). The compiler then checks that every path assigns it before use, which catches a forgotten else. When every branch compares one value against constants, a switch expression is usually clearer than a long chain.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Return 401, 403 or 405 from a handler with guard clauses before any business logic runs.
  • Pick a shipping rate or discount tier from an order total, testing the highest threshold first.
  • Read an optional Authorization header safely with a null check joined by &&.

More examples

Order matters in an else-if chain

The first true condition wins, so a broad check above a narrow one hides the narrow branch completely. The compiler accepts both versions, which is why tier and pricing rules deserve a test at each boundary.

Example · java
String tierWrong(long spent) {
    if (spent >= 100) {
        return "SILVER";
    } else if (spent >= 1000) {
        return "GOLD"; // never reached: 1000 is also >= 100
    } else {
        return "STANDARD";
    }
}

String tierRight(long spent) {
    if (spent >= 1000) {
        return "GOLD";
    } else if (spent >= 100) {
        return "SILVER";
    } else {
        return "STANDARD";
    }
}

void main() {
    for (long spent : new long[] {50, 250, 5000}) {
        IO.println(spent + ": wrong=" + tierWrong(spent) + " right=" + tierRight(spent));
    }
}
// Output:
// 50: wrong=STANDARD right=STANDARD
// 250: wrong=SILVER right=SILVER
// 5000: wrong=SILVER right=GOLD

Short-circuit && protects a null check

With &&, startsWith only runs when the header is not null, which is the standard way to read an optional request header. The single & is a valid boolean operator but never skips its right side.

Example · java
String userFrom(String header) {
    if (header != null && header.startsWith("Bearer ")) {
        return "token " + header.substring(7);
    }
    return "anonymous";
}

void main() {
    IO.println(userFrom("Bearer abc123"));
    IO.println(userFrom(null));
    IO.println(userFrom("Basic dXNlcg=="));

    String header = null;
    try {
        // A single & evaluates both sides, so startsWith runs on null.
        if (header != null & header.startsWith("Bearer ")) {
            IO.println("never printed");
        }
    } catch (NullPointerException e) {
        IO.println("single & evaluated both sides");
    }
}
// Output:
// token abc123
// anonymous
// anonymous
// single & evaluated both sides

instanceof patterns inside if

The pattern variable s or n is only in scope where the test has succeeded, so it can be used later in the same && condition. A blank string fails the first condition as a whole and falls through to the final else.

Example · java
String describe(Object body) {
    if (body instanceof String s && !s.isBlank()) {
        return "text of length " + s.length();
    } else if (body instanceof Integer n && n > 0) {
        return "positive id " + n;
    } else if (body == null) {
        return "empty body";
    } else {
        return "unsupported: " + body.getClass().getSimpleName();
    }
}

void main() {
    IO.println(describe("hello"));
    IO.println(describe("   "));
    IO.println(describe(42));
    IO.println(describe(null));
    IO.println(describe(3.5));
}
// Output:
// text of length 5
// unsupported: String
// positive id 42
// empty body
// unsupported: Double

Discussion

  • Be the first to comment on this lesson.