Exceptions
Things go wrong at run time: a file is missing, the network drops, a user types letters where a number belongs. Java reports such problems by throwing an exception, which travels up the call stack until some code catches it — or the program stops with a stack trace. Handling exceptions well is what makes programs robust.
An uncaught exception
public class Main { static int average(int[] values) { int sum = 0; for (int v : values) sum += v; return sum / values.length; } public static void main(String[] args) { System.out.println(average(new int[]{4, 8})); System.out.println(average(new int[]{})); System.out.println("never printed"); } }
6
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Main.average(Main.java:5)
at Main.main(Main.java:10)The stack trace shows the exception type, its message, and the chain of method calls — read it from the top: the problem happened in average at line 5, called from main at line 10.
try and catch
public class Main { public static void main(String[] args) { String[] inputs = {"42", "seven", "8"}; int total = 0; for (String input : inputs) { try { total += Integer.parseInt(input); } catch (NumberFormatException e) { System.out.println("Skipping '" + input + "': " + e.getMessage()); } } System.out.println("Total: " + total); } }
Skipping 'seven': For input string: "seven" Total: 50
Code that might fail goes in try. If an exception of the matching type is thrown, execution jumps to the catch block, then continues after it. The exception object carries a message and the stack trace.
Several catch blocks
public class Main { static String lookup(String[] items, String indexText) { try { int index = Integer.parseInt(indexText); return items[index].toUpperCase(); } catch (NumberFormatException e) { return "not a number"; } catch (ArrayIndexOutOfBoundsException | NullPointerException e) { return "no such item (" + e.getClass().getSimpleName() + ")"; } } public static void main(String[] args) { String[] items = {"apple", null, "cherry"}; System.out.println(lookup(items, "2")); System.out.println(lookup(items, "x")); System.out.println(lookup(items, "9")); System.out.println(lookup(items, "1")); } }
CHERRY not a number no such item (ArrayIndexOutOfBoundsException) no such item (NullPointerException)
Catch blocks are checked in order; A | B handles several types in one block. Catch specific types rather than Exception, so you don't accidentally hide bugs.
finally
public class Main { static int divide(int a, int b) { try { return a / b; } finally { System.out.println("finally runs for " + a + "/" + b); } } public static void main(String[] args) { System.out.println(divide(10, 2)); try { divide(1, 0); } catch (ArithmeticException e) { System.out.println("caught: " + e.getMessage()); } } }
finally runs for 10/2 5 finally runs for 1/0 caught: / by zero
A finally block always runs — after normal completion, after a return, and after an exception — so it's used for clean-up. For closing resources, try-with-resources (next lesson) is better.
Throwing exceptions
public class Main { static double withdraw(double balance, double amount) { if (amount <= 0) { throw new IllegalArgumentException("Amount must be positive: " + amount); } if (amount > balance) { throw new IllegalStateException("Insufficient funds"); } return balance - amount; } public static void main(String[] args) { System.out.println(withdraw(100, 30)); try { withdraw(100, 500); } catch (IllegalStateException e) { System.out.println("Refused: " + e.getMessage()); } } }
70.0 Refused: Insufficient funds
throw raises an exception. IllegalArgumentException (bad argument) and IllegalStateException (object not in the right state for this call) are the standard choices for rejecting invalid use.
Checked and unchecked exceptions
| Kind | Examples | Rule |
|---|---|---|
Checked (extend Exception) | IOException, SQLException | Must be caught or declared with throws |
Unchecked (extend RuntimeException) | NullPointerException, IllegalArgumentException | No requirement — usually programming errors |
Errors (extend Error) | OutOfMemoryError, StackOverflowError | Serious problems; don't catch them |
import java.nio.file.Files; import java.nio.file.Path; public class Main { static String readConfig() { return Files.readString(Path.of("config.txt")); } public static void main(String[] args) { } }
Main.java:6: error: unreported exception IOException; must be caught or declared to be thrown
Checked exceptions represent problems a correct program must still expect, like missing files. The compiler forces you to handle them — either with try/catch or by adding throws IOException to the method so the caller deals with it.
import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; public class Main { static String readConfig() throws IOException { return Files.readString(Path.of("config-that-does-not-exist.txt")); } public static void main(String[] args) { try { System.out.println(readConfig()); } catch (IOException e) { System.out.println("Using defaults: " + e.getClass().getSimpleName()); } } }
Using defaults: NoSuchFileException
Good practice
- Never swallow exceptions with an empty
catch {}. At least log them. - Catch where you can do something useful — retry, use a default, show a message. Otherwise let it propagate.
- Fail fast: validate arguments at the start of methods.
- Don't use exceptions for normal control flow; check conditions instead when you can.
Try it yourself
Parse a list of ages, collecting errors instead of stopping at the first one.
'-2' out of range: -2 'abc' is not a number 3 valid ages