If/Else and Switch Statements

8 min read

Branching is how a program reacts. The if/else and switch constructs decide which path to take when an HTTP status comes back, when a config flag flips, when a test result is good or bad. Java's syntax is almost identical to JavaScript's — almost. The one difference will bite every newcomer at some point: == works for numbers and booleans but does not work for comparing the contents of two Strings. Get that right and the rest of this lesson will feel familiar.

if / else if / else — the basic shape

public class StatusClassifier {
    public static void main(String[] args) {
        int statusCode = 502;
 
        if (statusCode == 200) {
            System.out.println("Test passed");
        } else if (statusCode >= 400 && statusCode < 500) {
            System.out.println("Client error");
        } else if (statusCode >= 500) {
            System.out.println("Server error");
        } else {
            System.out.println("Unexpected status: " + statusCode);
        }
    }
}

Compile and run:

javac StatusClassifier.java
java StatusClassifier

Output:

Server error

Same syntax as JavaScript: parentheses around the condition, braces around the body, an optional chain of else if clauses, an optional final else. Java requires the parentheses around the condition; you cannot write if statusCode == 200.

The braces are technically optional for a single-statement body — if (x == 1) doThing(); compiles. Don't take the shortcut. Always use braces. The Apple goto fail SSL bug from 2014 was caused by exactly this kind of brace-less if; modern Java style guides forbid it for the same reason.

The String comparison trap

This is the single most-asked Java question on Stack Overflow. Read it carefully.

String env = "staging";
 
if (env == "staging") {                  // ❌ DON'T DO THIS
    System.out.println("Hits sometimes");
}
if (env.equals("staging")) {             // ✅ ALWAYS DO THIS
    System.out.println("Hits reliably");
}

For primitives (int, boolean, double, char), == compares the value. For objects (String, arrays, anything with a class), == compares the reference — i.e., "are these two variables pointing at the exact same object in memory?" Two different String objects with identical text can compare unequal under ==.

The reason it sometimes seems to work for Strings is string interning: when you write a String literal like "staging", the JVM may reuse the same object for every identical literal. So env == "staging" is true if env happens to also be a literal. The moment the String comes from a CSV file, an HTTP response, or Integer.parseInt, the interning trick breaks and == returns false. Tests pass on your laptop and fail on the CI agent for "no reason." Always use .equals():

if (env.equals("staging")) { ... }            // safe
if (env.equalsIgnoreCase("STAGING")) { ... }  // also case-insensitive

A safer variant that survives null is to put the literal first: "staging".equals(env). If env is null, this still returns false instead of throwing NullPointerException. Many style guides recommend that flip for any value that might be null.

Switch — when you have many discrete cases

When you're branching on the same variable against several constant values, switch is more readable than a long if/else chain:

public class EnvUrl {
    public static void main(String[] args) {
        String env = "staging";
        String url;
 
        switch (env) {
            case "dev":
                url = "http://localhost:3000";
                break;
            case "staging":
                url = "https://staging.myapp.com";
                break;
            case "production":
                url = "https://myapp.com";
                break;
            default:
                throw new IllegalArgumentException("Unknown env: " + env);
        }
 
        System.out.println("Base URL: " + url);
    }
}

Output:

Base URL: https://staging.myapp.com

A few rules:

  • switch works on int, String (since Java 7), enums, and a handful of other types. It does not work on arbitrary objects.
  • Each case ends with break; — without it, execution falls through into the next case. This is almost never what you want and is a classic source of bugs.
  • The default case is the catch-all (like else). Throwing on unknown input — as we did above — is a strong pattern: it turns a typo in the env argument into a loud failure instead of a silent miss.

Enhanced switch (Java 14+) — the modern form

The arrow syntax fixes two warts at once: no break, and no fall-through. It's also an expression — it returns a value:

public class EnvUrlModern {
    public static void main(String[] args) {
        String env = "staging";
 
        String url = switch (env) {
            case "dev"        -> "http://localhost:3000";
            case "staging"    -> "https://staging.myapp.com";
            case "production" -> "https://myapp.com";
            default           -> throw new IllegalArgumentException("Unknown env: " + env);
        };
 
        System.out.println("Base URL: " + url);
    }
}

Output:

Base URL: https://staging.myapp.com

If you're on Java 14 or later (and you should be), prefer this form. It's shorter, clearer, and impossible to break with a missing break. You'll see the older switch form in lots of existing test code; both are still valid.

A real QA example — classify HTTP responses

This is the kind of helper you'll write inside a test framework:

public class ResponseClassifier {
    public static void main(String[] args) {
        int[] codes = {200, 301, 404, 502, 999};
 
        for (int code : codes) {
            String category = classify(code);
            System.out.println(code + " -> " + category);
        }
    }
 
    static String classify(int code) {
        if (code >= 200 && code < 300) return "success";
        if (code >= 300 && code < 400) return "redirect";
        if (code >= 400 && code < 500) return "client error";
        if (code >= 500 && code < 600) return "server error";
        return "unknown";
    }
}

Output:

200 -> success
301 -> redirect
404 -> client error
502 -> server error
999 -> unknown

Notice the return inside each if — once we find a match, we exit the method. No else chain needed. This is the same "early return" pattern you'd write in JavaScript or Python.

The decision tree, visualised

Reading the chart left to right: one input, five mutually exclusive branches, exactly the shape the classify method implements.

⚠️ Common mistakes

  • Comparing Strings with ==. env == "staging" looks right and works on your laptop. It will fail on a CI agent the day the value comes from an HTTP response. Always use env.equals("staging"). If the variable might be null, flip it: "staging".equals(env).
  • Forgetting break in a classic switch. Without it, execution continues into the next case. The compiler does not warn you. The fix is either to write the break every time, or use the arrow form (case "x" -> ...) which has no fall-through.
  • Brace-less if bodies. if (x) doA(); doB(); runs doB() unconditionally — the indentation is misleading. Always wrap bodies in { }. This is a documented source of real-world security bugs.

🎯 Practice task

Build a test result classifier. 25 minutes.

  1. Create ResultClassifier.java with a public class ResultClassifier.
  2. In main, declare String[] envs = {"dev", "staging", "production", "qa"}; and int[] codes = {200, 301, 404, 503};.
  3. Write a method static String envUrl(String env) that uses an enhanced switch (Java 14+) to return the right base URL for dev, staging, production, and throws IllegalArgumentException for anything else.
  4. Write a method static String classify(int code) using if/else if to return "success", "redirect", "client error", "server error", or "unknown".
  5. In main, loop over both arrays and print:
    dev -> http://localhost:3000
    staging -> https://staging.myapp.com
    ...
    200 -> success
    301 -> redirect
    ...
    
  6. Compile and run. Confirm the unknown env ("qa") throws an IllegalArgumentException with a useful message.
  7. Stretch: add a String env = null; test and call envUrl(env). Catch the NullPointerException. Then switch your switch to use "dev".equals(env) style if/else and confirm the null no longer crashes — it falls into the default. Defensive String comparison is a habit worth building early.

You can now branch on values reliably. Lesson 2 covers the operators (&&, ||, instanceof, ternary) you use to build the conditions inside an if.

// tip to track lessons you complete and pick up where you left off across devices.