Java course menu
Java Exceptions: try, catch, finally, throw, Checked vs Unchecked
Learn Java exception handling: try, catch and finally, multiple and multi-catch blocks, the exception hierarchy, checked vs unchecked exceptions, throw and throws, custom exceptions, try-with-resources, stack traces, and best practices.
On this page
An exception is an object that signals that something went wrong: a file is missing, text is not a number, an index is out of range. Instead of crashing immediately, Java lets you catch exceptions and decide what to do, and lets methods throw them to report problems they cannot handle themselves.
try and catch#
public class Main {
public static void main(String[] args) {
try {
int[] values = {10, 20, 30};
System.out.println(values[1]);
System.out.println(values[5]);
System.out.println("this line is skipped");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: " + e.getMessage());
}
System.out.println("program continues");
}
}20
Error: Index 5 out of bounds for length 3
program continuesWhen an exception is thrown, the rest of the try block is skipped and the first matching catch runs. e.getMessage() describes the problem.
Several catch blocks and multi-catch#
public class Main {
static void process(String input) {
try {
int index = Integer.parseInt(input);
int[] data = {10, 20, 30};
System.out.println(data[index]);
} catch (NumberFormatException e) {
System.out.println(input + ": not a number");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(input + ": no such position");
}
}
public static void main(String[] args) {
process("1");
process("7");
process("x");
}
}20
7: no such position
x: not a numberWhen several exceptions are handled the same way, catch them together with |:
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
System.out.println("Invalid input: " + e.getMessage());
}Catch blocks are checked in order, so put specific exception types before general ones.
finally#
A finally block runs whether or not an exception occurred, which makes it the place for clean-up:
public class Main {
static void divide(int a, int b) {
try {
System.out.println("Result: " + a / b);
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero");
} finally {
System.out.println("done");
}
}
public static void main(String[] args) {
divide(10, 4);
divide(1, 0);
}
}Result: 2
done
Cannot divide by zero
doneFor closing files and connections, try-with-resources (below) is better than finally.
The exception hierarchy#
All exceptions derive from Throwable:
Error: serious JVM problems such asOutOfMemoryErrorandStackOverflowError. Do not catch these in normal code.Exception: problems a program may handle.- Checked exceptions: all
Exceptionsubclasses exceptRuntimeException. Examples:IOException,FileNotFoundException. The compiler forces you to handle or declare them. - Unchecked exceptions:
RuntimeExceptionand its subclasses, such asNullPointerException,IllegalArgumentException,ArithmeticException,IndexOutOfBoundsExceptionandNumberFormatException. These usually indicate programming errors, and the compiler does not force you to handle them.
- Checked exceptions: all
Checked exceptions and throws#
A method that can throw a checked exception must either catch it, or declare it with throws so that callers deal with it:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
static String readConfig(String fileName) throws IOException {
return Files.readString(Path.of(fileName)); // may throw IOException
}
public static void main(String[] args) {
try {
System.out.println(readConfig("missing-config.txt"));
} catch (IOException e) {
System.out.println("Could not read the config: " + e.getClass().getSimpleName());
}
}
}Could not read the config: NoSuchFileExceptionWithout throws IOException on readConfig, or a try/catch around the call in main, the code does not compile.
Throwing exceptions#
Use throw to signal a problem yourself. For invalid arguments, the standard choice is IllegalArgumentException; for an object in the wrong state, IllegalStateException:
public class Main {
static int parseAge(String text) {
int age = Integer.parseInt(text.strip());
if (age < 0 || age > 130) {
throw new IllegalArgumentException("age must be 0-130, got " + age);
}
return age;
}
public static void main(String[] args) {
for (String input : new String[] {"25", "abc", "200"}) {
try {
int age = parseAge(input);
System.out.println("age " + age);
} catch (NumberFormatException e) {
System.out.println(input + ": not a number");
} catch (IllegalArgumentException e) {
System.out.println(input + ": " + e.getMessage());
}
}
}
}age 25
abc: not a number
200: age must be 0-130, got 200NumberFormatException is a subclass of IllegalArgumentException, so it must be caught first; otherwise the general block would catch it.
Custom exceptions#
Create your own exception type by extending Exception (checked) or RuntimeException (unchecked):
class InsufficientFundsException extends RuntimeException {
private final double shortfall;
InsufficientFundsException(double balance, double requested) {
super("cannot withdraw " + requested + ", balance is " + balance);
this.shortfall = requested - balance;
}
double getShortfall() {
return shortfall;
}
}
class Account {
private double balance;
Account(double balance) {
this.balance = balance;
}
void withdraw(double amount) {
if (amount > balance) {
throw new InsufficientFundsException(balance, amount);
}
balance -= amount;
}
}
public class Main {
public static void main(String[] args) {
Account acc = new Account(500);
try {
acc.withdraw(200);
acc.withdraw(800);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage() + " (short by " + e.getShortfall() + ")");
}
}
}cannot withdraw 800.0, balance is 300.0 (short by 500.0)try-with-resources#
Objects that hold resources (files, network connections, database connections) implement AutoCloseable. Declaring them in the parentheses of a try closes them automatically, even if an exception occurs:
class Connection implements AutoCloseable {
private final String name;
Connection(String name) {
this.name = name;
System.out.println("open " + name);
}
void query() {
throw new IllegalStateException("query failed");
}
@Override
public void close() {
System.out.println("close " + name);
}
}
public class Main {
public static void main(String[] args) {
try (Connection db = new Connection("database")) {
db.query();
} catch (IllegalStateException e) {
System.out.println("caught: " + e.getMessage());
}
}
}open database
close database
caught: query failedThe resource is closed before the catch block runs. You will use try-with-resources for every file in File Handling.
Stack traces#
An uncaught exception ends the program and prints a stack trace: the exception type, its message, and the chain of method calls that led to it, most recent first:
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
at java.base/java.lang.Integer.parseInt(Integer.java:661)
at Main.parseAge(Main.java:3)
at Main.main(Main.java:11)Read it from the top: the first line of your code (Main.java:3) is usually where to look.
Best practices#
- Catch specific exceptions, not
ExceptionorThrowable. - Never swallow exceptions with an empty
catchblock; at least log them. - Use exceptions for exceptional situations, not for normal control flow.
- Prefer unchecked exceptions for programming errors, and checked ones for recoverable conditions the caller must consider.
- Use try-with-resources for anything that must be closed.
Common mistakes#
Practice#
Practice exercise
Write double safeAverage(String[] items) that converts each item with Double.parseDouble, skips invalid items (printing skipping <item>), and throws IllegalArgumentException("no valid numbers") if none are valid. Call it with {"4", "x", "8"} and with {"a", "b"}.
Show solution
public class SafeAverage {
static double safeAverage(String[] items) {
double sum = 0;
int count = 0;
for (String item : items) {
try {
sum += Double.parseDouble(item);
count++;
} catch (NumberFormatException e) {
System.out.println("skipping " + item);
}
}
if (count == 0) {
throw new IllegalArgumentException("no valid numbers");
}
return sum / count;
}
public static void main(String[] args) {
System.out.println(safeAverage(new String[] {"4", "x", "8"}));
try {
safeAverage(new String[] {"a", "b"});
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
}
}skipping x
6.0
skipping a
skipping b
Error: no valid numbersKey takeaway#
Finished this lesson?