Java · Lesson 6 of 6
Exception Handling
try/catch/finally and throwing checked exceptions.
- Intermediate
- 13 min read
- 3 objectives
Before this lessonLesson 5: Collections Framework
What you will learn
- Catch NumberFormatException
- Use finally
- Throw IllegalArgumentException
When something goes wrong at runtime, Java throws an exception object and unwinds the call stack until some code catches it. If nothing does, the thread ends with a stack trace. Handling exceptions well is the difference between a program that crashes mysteriously and one that fails clearly.
When things go wrong
Files are missing, numbers arrive as text, networks fail. An exception is Java's way of saying "something went wrong here" and unwinding until some code decides how to handle it. Without handling, the program prints a stack trace and stops. With try/catch you keep control and respond sensibly, for example by showing a friendly message or trying again.
Checked and unchecked
- Checked exceptions (such as
IOException) describe problems outside your control. The compiler forces you to catch them or declare them withthrows. - Unchecked exceptions extend
RuntimeException(such asNullPointerExceptionorIllegalArgumentException). They usually signal a bug, and you are not forced to catch them.
try / catch / finally
try {
int n = Integer.parseInt("abc");
System.out.println(n);
} catch (NumberFormatException e) {
System.out.println("bad number: " + e.getMessage());
} finally {
System.out.println("always runs");
}bad number: For input string: "abc" always runs
List catch blocks from most specific to most general; the first match wins. You can also catch several types in one block with catch (IOException | SQLException e).
try-with-resources
Anything that must be closed (files, connections) should be opened in the parentheses of a try. Java closes it automatically, even when an exception is thrown, so you never need a manual finally for cleanup.
import java.io.*;
import java.nio.file.*;
try (BufferedReader in = Files.newBufferedReader(Path.of("notes.txt"))) {
System.out.println(in.readLine());
} catch (IOException e) {
System.out.println("cannot read file: " + e.getMessage());
}Throwing your own
Validate input early and throw an exception whose message says exactly what was wrong. When you catch one exception and throw another, pass the original as the cause so the trace is not lost.
public static int parsePositive(String raw) {
try {
int n = Integer.parseInt(raw);
if (n <= 0) throw new IllegalArgumentException("must be positive: " + n);
return n;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("not a number: " + raw, e);
}
}Catching a specific exception
public class Main {
public static void main(String[] args) {
String[] inputs = {"42", "abc", "7"};
for (String s : inputs) {
try {
int n = Integer.parseInt(s);
System.out.println("parsed " + n);
} catch (NumberFormatException e) {
System.out.println("not a number: " + s);
} finally {
System.out.println("(done with " + s + ")");
}
}
}
}parsed 42 (done with 42) not a number: abc (done with abc) parsed 7 (done with 7)
The loop keeps going after the bad input; the exception was contained. finally runs whether or not an exception happened, which is the right place for cleanup.
Reading a stack trace
An uncaught exception prints where it started and how the program got there. Read the first line (the type and message) and then the first line that mentions your code.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Main.divide(Main.java:4)
at Main.main(Main.java:9)Throwing an exception with a clear message
public class Main {
static double average(int[] nums) {
if (nums.length == 0) {
throw new IllegalArgumentException("need at least one number");
}
int sum = 0;
for (int n : nums) sum += n;
return (double) sum / nums.length;
}
public static void main(String[] args) {
System.out.println(average(new int[]{2, 4, 6}));
try {
average(new int[]{});
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
}
}4.0 Error: need at least one number
Checked versus unchecked, in plain words
- Checked exceptions (such as
IOException) are things the compiler forces you to deal with, because they can happen even in correct code (a file may be missing). - Unchecked exceptions (such as
NullPointerException) usually mean a bug in the code; you fix the code rather than catch them everywhere.
Letting Java close resources for you
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
System.out.println(reader.readLine());
} catch (IOException e) {
System.out.println("could not read file: " + e.getMessage());
}
// the reader is closed automatically, even if an exception was thrownCommon mistakes
- Empty catch blocks (
catch (Exception e) {}) hide failures. At least log the error. - Catching
ExceptionorThrowabletoo broadly, which also swallows bugs. - Using exceptions for normal control flow; they are slow and obscure intent.
- Losing the original cause by not passing it to the new exception.
Key takeaways
- Wrap risky code in
tryand catch the specific exception you expect. finallyalways runs; try-with-resources closes files and connections automatically.- Throw your own exceptions with clear messages when input is invalid.
- Read stack traces top-down: type and message first, then the first line of your own code.
// Write your solution here
