Things go wrong in every real program. Users type letters where numbers belong, files are missing, networks drop and divisions by zero sneak in. When Java meets a problem it can't continue from, it throws an exception: an object describing what went wrong. If nothing catches it, the program stops and prints a stack trace.
Handling exceptions well is what separates a program that crashes at the first surprise from one that reports a clear message, recovers where it can and cleans up after itself. Java takes this seriously: for some kinds of problem, the compiler won't even let you ignore the possibility of an error.
In this lesson you'll read stack traces, catch exceptions with try and catch, run clean-up code with finally and try-with-resources, learn the difference between checked and unchecked exceptions, and throw exceptions of your own, including custom exception classes.
Reading a stack trace
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Main.average(Main.java:9)
at Main.main(Main.java:4)- The first line names the exception type,
ArithmeticException, and its message,/ by zero. - The
atlines show the chain of method calls, most recent first. The top one,Main.java:9, is where the problem happened; below it is who called that method.
Start at the top, open that line of code and ask what value could have caused the message. Most bugs are found in a minute this way.
try, catch and finally
try {
int n = Integer.parseInt(text);
System.out.println(100 / n);
} catch (NumberFormatException e) {
System.out.println("Not a number: " + text);
} catch (ArithmeticException e) {
System.out.println("Can't divide by zero");
} finally {
System.out.println("Done");
}- Code in
tryruns normally until an exception is thrown; Java then jumps to the first matchingcatch. - Order
catchblocks from most specific to most general, because the first match wins. - One block can catch several types:
catch (NumberFormatException | ArithmeticException e). finallyruns whether or not an exception happened, even after areturn, which makes it the place for clean-up.
Checked and unchecked exceptions
Java has two families of exceptions:
| Kind | Examples | Rule |
|---|---|---|
Unchecked (extend RuntimeException) | NullPointerException, ArithmeticException, IndexOutOfBoundsException, NumberFormatException | Usually programming mistakes; catching is optional |
Checked (extend Exception) | IOException, FileNotFoundException, SQLException | Problems outside your control; you must catch them or declare them |
To declare a checked exception instead of handling it, add throws to the method: void load(String path) throws IOException. The caller then has to deal with it. This forces every part of the program to think about failures such as missing files.
throw, custom exceptions and try-with-resources
Use throw to signal a problem yourself, usually when a method receives an invalid argument:
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}For problems specific to your program, create your own exception class by extending Exception (checked) or RuntimeException (unchecked).
Resources such as files and network connections must be closed. try-with-resources closes them automatically, even if an exception is thrown:
try (BufferedReader reader = Files.newBufferedReader(Path.of("data.txt"))) {
System.out.println(reader.readLine());
}Wrapping exceptions with a cause
Low-level exceptions often make little sense to the code that called you. A NumberFormatException deep inside a settings loader means "the configuration is broken" to the rest of the program. Catch the low-level exception and throw a more meaningful one, passing the original as the cause:
try {
timeout = Integer.parseInt(settings.get("timeout"));
} catch (NumberFormatException e) {
throw new IllegalStateException("Bad config value for timeout", e);
}The cause is kept, so the stack trace shows both: your message first, then a Caused by: java.lang.NumberFormatException section pointing at the real source. e.getCause() returns it in code. Never throw away the original exception when wrapping; it's the most useful clue you have.
When to use exceptions
Exceptions are for unusual situations, not ordinary decisions. Checking if (text.isBlank()) is clearer and faster than letting a parse fail and catching the result. Good rules of thumb:
- Validate inputs you can check cheaply, and throw
IllegalArgumentExceptionwhen a method receives a value it can't accept. - Catch an exception only where you can do something useful: retry, use a default, or show a helpful message.
- Otherwise let it travel up to a place that can report it, such as
mainor a web framework's error handler.
The exception hierarchy and try-catch-finally lessons practise these decisions in more depth.
Java Exceptions Syntax
Exception syntax
try {
risky();
} catch (SpecificException e) {
System.out.println(e.getMessage());
} catch (OtherException | ThirdException e) {
// handle either
} finally {
// always runs
}
void method() throws IOException { }
throw new IllegalArgumentException("message");
class MyException extends Exception {
MyException(String msg) { super(msg); }
}Java Exceptions Summary
- Read stack traces from the top: the type, message and line where it happened.
trywraps risky code;catch (Type e)handles a specific exception;finallyalways runs.- Unchecked exceptions extend RuntimeException; checked ones must be caught or declared with
throws. throw new IllegalArgumentException("...")reports invalid input clearly.- try-with-resources closes files and connections automatically.
Java Exceptions Examples
Each example shows the code, the output it produces and what each part does.
Handling bad input
public class Main {
static void showHalf(String text) {
try {
int n = Integer.parseInt(text.trim());
System.out.println(text.trim() + " / 2 = " + (n / 2));
} catch (NumberFormatException e) {
System.out.println("'" + text + "' isn't a whole number");
}
}
public static void main(String[] args) {
showHalf("18");
showHalf("twelve");
showHalf(" 7 ");
}
}18 / 2 = 9
'twelve' isn't a whole number
7 / 2 = 3Line-by-line explanation
- Line 4parseInt throws NumberFormatException for text that isn't a number.
- Lines 6 to 8The program reports the problem and carries on with the next value.
- Line 14Integer division: 7 / 2 is 3.
finally and the order of events
public class Main {
static int divide(int a, int b) {
try {
System.out.println("Dividing " + a + " by " + b);
return a / b;
} catch (ArithmeticException e) {
System.out.println("Error: " + e.getMessage());
return 0;
} finally {
System.out.println("Finished " + a + "/" + b);
}
}
public static void main(String[] args) {
System.out.println("Result: " + divide(10, 2));
System.out.println("Result: " + divide(5, 0));
}
}Dividing 10 by 2
Finished 10/2
Result: 5
Dividing 5 by 0
Error: / by zero
Finished 5/0
Result: 0Line-by-line explanation
- Line 5The return value is ready, but finally still runs before the method actually returns.
- Lines 6 to 8Dividing an int by zero throws ArithmeticException.
- Lines 9 to 11finally runs in both cases.
A custom exception
class InsufficientFundsException extends Exception {
InsufficientFundsException(double needed) {
super(String.format("Need £%.2f more", needed));
}
}
class Account {
private double balance = 50;
void withdraw(double amount) throws InsufficientFundsException {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
if (amount > balance) {
throw new InsufficientFundsException(amount - balance);
}
balance -= amount;
}
}
public class Main {
public static void main(String[] args) {
Account acc = new Account();
try {
acc.withdraw(30);
acc.withdraw(30);
} catch (InsufficientFundsException e) {
System.out.println("Declined: " + e.getMessage());
}
}
}Declined: Need £10.00 moreLine-by-line explanation
- Line 1Extending Exception makes it a checked exception.
- Line 10throws warns callers that this method can fail this way.
- Line 12An unchecked exception for a programming mistake: a non-positive amount.
- Line 15A checked exception for a normal business situation.
- Lines 26 to 28The second withdrawal fails: 30 is 10 more than the remaining 20.
Common Java Exceptions Mistakes
Catching Exception and ignoring it
try {
saveOrder(order);
} catch (Exception e) { }try {
saveOrder(order);
} catch (IOException e) {
System.err.println("Couldn't save order: " + e.getMessage());
}General catch before specific
try { ... }
catch (Exception e) { ... }
catch (NumberFormatException e) { ... } // compile error: already caughttry { ... }
catch (NumberFormatException e) { ... }
catch (Exception e) { ... }Forgetting to close a file
BufferedReader r = Files.newBufferedReader(path);
String line = r.readLine(); // if this throws, the file stays opentry (BufferedReader r = Files.newBufferedReader(path)) {
String line = r.readLine();
}Java Exceptions in Real Projects
Exceptions in real systems
Web frameworks such as Spring catch exceptions thrown while handling a request and turn them into proper error responses, such as "404 Not Found" or "400 Bad Request". Banking code uses custom exceptions like InsufficientFundsException to make business rules explicit, and every file, database and network API in Java reports failures through checked exceptions.
Java Exceptions FAQ
What is the difference between throw and throws in Java?
throw is a statement that actually throws an exception object: throw new IllegalArgumentException("..."). throws appears in a method declaration and warns callers which checked exceptions the method may pass on: void load() throws IOException.
Does finally always run?
It runs after the try block whether it completes normally, returns or throws, and after any catch. The exceptions are extreme cases: the program calls System.exit(), the JVM crashes or is killed, or the thread never finishes the try block. Don't put a return inside finally, because it hides any exception thrown earlier.
What is the difference between an Exception and an Error?
Both extend Throwable. Exceptions describe problems a program can reasonably handle, such as bad input or a missing file. Errors, such as OutOfMemoryError and StackOverflowError, signal serious problems in the JVM or the program's resources, and ordinary code shouldn't try to catch them.
Should my custom exception be checked or unchecked?
Make it unchecked, extending RuntimeException, when it signals a programming mistake or a situation callers usually can't fix. Make it checked, extending Exception, when callers can reasonably recover and you want the compiler to remind them, such as a declined payment. Many modern libraries prefer unchecked exceptions for simplicity.
How do I print the full details of an exception?
e.printStackTrace() prints the type, message, stack trace and any causes to the error output, which is fine while learning. In real applications, pass the exception to a logger, such as logger.error("Import failed", e), so the details are stored with the rest of the program's logs.
Java Exceptions Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the code so the error is handled.
{
int n = Integer.parseInt("abc");
} (NumberFormatException e) {
System.out.println("Invalid");
}
What does this print?
try {
int[] a = new int[2];
a[5] = 1;
System.out.println("A");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("B");
}
What does this print?
try {
System.out.println("1");
} finally {
System.out.println("2");
}
System.out.println("3");
Which exception is checked?
What does try-with-resources do?
Java Exceptions Quiz
5 questions to check what you learned.
Which keyword signals an exception yourself?
- throws
- catch
- raise
- throw
What's the difference between
throwandthrows?- They're the same
- throw raises an exception; throws declares that a method may raise one
- throws raises an exception; throw declares one
- throw is for checked exceptions only
When does a finally block run?
- Only after an exception
- Only without an exception
- In both cases, even after a return
- Only in main
Which class do unchecked exceptions extend?
- RuntimeException
- Error
- IOException
- Throwable directly
Why must specific catch blocks come before general ones?
- It's faster
- Java sorts them automatically
- General catches only run last anyway
- The first matching catch wins, so a general one first would make the specific one unreachable