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 StatusClassifierOutput:
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-insensitiveA 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:
switchworks onint,String(since Java 7), enums, and a handful of other types. It does not work on arbitrary objects.- Each
caseends withbreak;— without it, execution falls through into the next case. This is almost never what you want and is a classic source of bugs. - The
defaultcase is the catch-all (likeelse). Throwing on unknown input — as we did above — is a strong pattern: it turns a typo in theenvargument 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 useenv.equals("staging"). If the variable might be null, flip it:"staging".equals(env). - Forgetting
breakin a classicswitch. Without it, execution continues into the next case. The compiler does not warn you. The fix is either to write thebreakevery time, or use the arrow form (case "x" -> ...) which has no fall-through. - Brace-less
ifbodies.if (x) doA(); doB();runsdoB()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.
- Create
ResultClassifier.javawith apublic class ResultClassifier. - In
main, declareString[] envs = {"dev", "staging", "production", "qa"};andint[] codes = {200, 301, 404, 503};. - Write a method
static String envUrl(String env)that uses an enhanced switch (Java 14+) to return the right base URL fordev,staging,production, and throwsIllegalArgumentExceptionfor anything else. - Write a method
static String classify(int code)using if/else if to return"success","redirect","client error","server error", or"unknown". - In
main, loop over both arrays and print:dev -> http://localhost:3000 staging -> https://staging.myapp.com ... 200 -> success 301 -> redirect ... - Compile and run. Confirm the unknown env (
"qa") throws anIllegalArgumentExceptionwith a useful message. - Stretch: add a
String env = null;test and callenvUrl(env). Catch theNullPointerException. Then switch your switch to use"dev".equals(env)style if/else and confirm the null no longer crashes — it falls into thedefault. 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.