if and switch statements and identify legal argument types for these statements.break and continue, and state the values taken by loop counter variables during and after loop execution.try, catch, finally) and declare methods, including overriding methods, that throw exceptions.if and switch. Both let a program choose between different paths of execution, but they differ in the kinds of values they can test and in how the exam expects you to reason about them.
if statement evaluates a boolean expression, and only a boolean expression. Unlike some other languages, Java will not implicitly convert an int to a boolean, so code such as if (count) will not compile.
int score = 82;
if (score >= 90) {
System.out.println("Grade: A");
} else if (score >= 80) {
System.out.println("Grade: B");
} else {
System.out.println("Grade: C or below");
}
Each else if is evaluated only when every preceding condition was false, and at most one branch runs. Watch for dangling-else problems on the exam: an else always binds to the nearest unmatched if, no matter how the code is indented. The ternary operator (condition ? valueIfTrue : valueIfFalse) is a compact alternative to a simple if/else when both branches produce a value:
String status = (score >= 60) ? "Pass" : "Fail";
switch can test a limited set of types: byte, short, char, int and their wrapper classes, String, enum constants, and, in modern Java, sealed type hierarchies through pattern matching. It cannot test long, float, double, or boolean; those aren't legal argument types, and code that tries to switch on them will not compile.
Traditional switch statements fall through by default, so each case needs an explicit break unless you intend execution to continue into the next case:
int day = 3;
String name;
switch (day) {
case 1:
name = "Monday";
break;
case 2:
name = "Tuesday";
break;
case 3:
name = "Wednesday";
break;
default:
name = "Unknown";
}
Modern Java adds the switch expression, which uses the arrow (->) syntax, does not fall through, and can return a value directly:
String name = switch (day) {
case 1 -> "Monday";
case 2 -> "Tuesday";
case 3 -> "Wednesday";
default -> "Unknown";
};
Java SE 21 goes a step further with pattern matching for switch, which lets you switch on a sealed type and match on its actual implementation without any casting:
sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
static double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Square s -> s.side() * s.side();
};
}
Because Shape is sealed and permits only Circle and Square, the compiler can verify the switch is exhaustive, so no default branch is required. The exam frequently tests whether you can spot a missing break in a traditional switch, so trace fall-through cases carefully before assuming the "obvious" output is correct.
for loop, the enhanced for loop (sometimes called a for-each loop), the while loop, and the do-while loop. Each repeats a block of code, but they differ in when the loop condition is checked and how the loop variable is managed.
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
// i is out of scope here; the for loops counter does not survive the loop
int j = 0;
while (j < 5) {
System.out.println(j);
j++;
}
// j equals 5 here, because the while loop does not create its own scope
int k = 0;
do {
System.out.println(k);
k++;
} while (k < 5);
// a do-while always executes its body at least once, even if the condition starts false
A basic for loop declares its counter in the initialization clause, so that variable goes out of scope the moment the loop ends. A while or do-while loop, by contrast, typically uses a variable declared before the loop, so that variable keeps whatever value it held when the loop condition finally evaluated to false. Where the counter lives, and what value it holds after the loop finishes, is one of the more commonly missed points on the exam.
The enhanced for loop iterates over arrays and any type that implements Iterable, without exposing an index:
int[] scores = {70, 85, 90};
int total = 0;
for (int score : scores) {
total += score;
}
Because there is no index variable, you cannot use an enhanced for loop to iterate backward, to skip elements, or to modify the underlying array by index; if you need any of that, reach for a basic for loop instead.
break exits the innermost enclosing loop or switch, and an unlabeled continue skips to the next iteration of the innermost enclosing loop. When you need to affect an outer loop from inside a nested one, label the outer loop:
outer:
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
if (col == row) {
continue outer;
}
if (row == 2 && col == 2) {
break outer;
}
System.out.println(row + "," + col);
}
}
A label must sit directly before the loop it names, and it can only be referenced by a break or continue nested inside that specific loop. Expect at least one exam question that asks you to trace exactly how many lines a labeled loop like this one prints, and common mistakes to watch for include off-by-one errors in the boundary condition, forgetting to update the loop variable (which produces an infinite loop), and assuming an unlabeled break reaches further than the loop it's actually written in.
new lives on the heap, and Java's garbage collector is responsible for reclaiming the memory of any object your program can no longer reach. "Reachable" means there is a live reference chain, starting from a running thread's stack, a static field, or JNI, that leads to the object. Once no such chain exists, the object becomes eligible for collection.
You can make an object eligible for collection in a few common ways:
Employee employee = new Employee("Ana");
employee = null; // the original Employee object is now unreachable
StringBuilder builder = new StringBuilder("draft");
builder = new StringBuilder("final"); // the "draft" object is now unreachable
void createLocal() {
Employee temp = new Employee("Ben");
} // temp goes out of scope when the method returns; the object becomes eligible
Java guarantees that the garbage collector will run, but it makes no guarantee about when it runs or in what order objects are reclaimed. Calling System.gc() only sends a suggestion to the JVM; it does not force collection to happen. The finalize() method, once used to run cleanup code before an object was collected, is deprecated and should not be relied on. If you need deterministic cleanup, use try-with-resources and the AutoCloseable interface instead, both of which are covered in the exception handling section below.
if and switch) and looping statements (for, enhanced for, while, and do-while) define the flow of control in your code, while garbage collection quietly manages memory in the background so you rarely need to think about it directly. Together, these two topics form the foundation the rest of this module builds on.
Java Throwables: Exceptions and Errors
Modern Java, 2026: what the compiler checks and what your code should handle.
Throwable hierarchy:
Throwable
Exception
RuntimeExceptionErrorImportant: Error is not an Exception. Both extend Throwable.
Checked exceptions: The compiler requires them to be caught or declared with throws. They usually represent recoverable external conditions. Recover, translate, or propagate them intentionally. Examples include IOException, SQLException, and ParseException.
Runtime exceptions: These are unchecked, so there is no catch-or-declare requirement. They usually indicate a programming or precondition problem. Fix the cause and catch them only at useful boundaries. Examples include NullPointerException, IllegalArgumentException, and IndexOutOfBoundsException.
Errors: These are unchecked and usually indicate a serious JVM or environment failure. Generally, application code should not catch them; allow termination or recovery infrastructure to respond. Examples include OutOfMemoryError, StackOverflowError, and LinkageError.
Shared behavior: Every category extends Throwable and can carry a message, cause, and stack trace.
try block contains code that might throw, a catch block handles a specific exception type, and an optional finally block runs whether or not an exception occurred.
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero: " + e.getMessage());
} finally {
System.out.println("Cleanup runs either way");
}
You can catch several unrelated exception types in a single block with multi-catch, separating the types with a pipe character:
try {
riskyOperation();
} catch (IOException | SQLException e) {
logger.error("Operation failed", e);
}
For resources that need to be closed, such as streams or connections, prefer try-with-resources over a manual finally block. Any class implementing AutoCloseable can be declared in the parentheses after try, and Java guarantees it will be closed, in reverse order of declaration, even if an exception is thrown:
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("Could not read file: " + e.getMessage());
}
throws in the method signature:
void readConfig() throws IOException {
Files.readAllLines(Path.of("config.txt"));
}
When you override a method, the exam expects you to know the rule for checked exceptions: an overriding method may throw the same checked exceptions as the superclass method, fewer of them, or subclasses of them, but it may never throw a new or broader checked exception. Unchecked exceptions aren't restricted this way; an overriding method can throw any RuntimeException, regardless of what the superclass method declares.
class Repository {
void save() throws IOException { }
}
class FileRepository extends Repository {
// Allowed: FileNotFoundException is a subclass of IOException
@Override
void save() throws FileNotFoundException { }
}
Exception to create a new checked exception, or extend RuntimeException to create a new unchecked one:
class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
class InvalidAccountException extends RuntimeException {
public InvalidAccountException(String message) {
super(message);
}
}
Choose a checked exception when callers can reasonably be expected to recover from the condition, such as a missing file or a failed network call. Choose an unchecked exception when the problem reflects a programming error, such as passing an invalid argument, that callers should fix in their code rather than handle at runtime.
It's important to have a crystal-clear understanding of these three categories of exceptions: what the compiler enforces, what typically causes each type to be thrown, and how each one should be handled.
break in a traditional switch statement, causing execution to fall through into the next case.else attaches to the "obviously intended" if, when it actually binds to the nearest unmatched one.while and do-while: only do-while guarantees at least one execution of the loop body.for loop's counter goes out of scope after the loop, while a variable declared before a while loop does not.System.gc() forces collection; it only requests it.