Exception Handling in Depth
Go beyond basic try/catch: finally, throwing your own errors, the checked-vs-unchecked split, and writing custom exceptions.
What you will learn
- Use finally and throw/throws correctly
- Tell checked from unchecked exceptions
- Write and throw your own custom exception
A quick recap
You already know try / catch: put risky code in try, handle problems in catch, and the program survives instead of crashing. This lesson completes the picture with the parts professional code relies on every day.
finally: code that always runs
A finally block runs no matter what — whether the try succeeded, an exception was caught, or even if none matched. It is the perfect place for clean-up that must happen either way, like closing a file or a database connection.
public class Main {
public static void main(String[] args) {
try {
System.out.println("Opening resource...");
int x = 10 / 0; // throws an exception
System.out.println("This line is skipped.");
} catch (ArithmeticException e) {
System.out.println("Caught: " + e.getMessage());
} finally {
System.out.println("Closing resource (always runs).");
}
}
}Note: Output:
Opening resource...
Caught: / by zero
Closing resource (always runs).
The division threw an exception, so the catch ran and printed the message (e.getMessage() gave the detail "/ by zero"). Then the finally block ran too. Even if there had been no error, finally would still have run — that guaranteed clean-up is its whole purpose.
Throwing your own errors with throw
Sometimes you want to signal a problem — for example, a negative age makes no sense. The keyword throw raises an exception on purpose. The caller then has to deal with it.
public class Main {
static void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative: " + age);
}
System.out.println("Age set to " + age);
}
public static void main(String[] args) {
setAge(30);
setAge(-5); // this call throws
System.out.println("This never prints.");
}
}Note: Output:
Age set to 30
Exception in thread "main" java.lang.IllegalArgumentException: Age cannot be negative: -5
The first call was fine. The second hit the throw, which raised an exception with our message. Because no try / catch wrapped it, the program stopped there — the last line never ran. Throwing lets a method refuse bad data clearly.
Checked vs unchecked exceptions
Java splits exceptions into two groups, and the difference decides whether you are forced to handle them.
- Unchecked (like
ArithmeticException,NullPointerException) usually come from programming bugs. Java does not force you to catch them — you fix the bug instead. - Checked (like
IOExceptionwhen reading a file) are problems Java expects might happen in normal use. Java forces you to either catch them or declare them withthrows, or your code will not compile.
The word throws (with an s, on the method line) is a warning label that says "calling this method might raise this exception — be ready". It passes the responsibility up to whoever calls the method.
import java.io.IOException;
public class Main {
// "throws IOException" warns callers this might fail
static void readData() throws IOException {
throw new IOException("Could not read the file");
}
public static void main(String[] args) {
try {
readData();
} catch (IOException e) {
System.out.println("Handled: " + e.getMessage());
}
}
}Note: Output:
Handled: Could not read the file
IOException is a checked exception, so readData had to declare throws IOException — without it the file would not compile. The main method then chose to handle it in a try / catch. Checked exceptions force you to plan for failures that are part of normal life, like a missing file.
Writing your own custom exception
For your own programs you can invent meaningful exception types. A custom exception is just a class that extends Exception (checked) or RuntimeException (unchecked). A clear name like InsufficientFundsException makes the error self-explanatory.
// 1) Define the custom exception
public class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message); // pass the message up to Exception
}
}
// 2) Throw it where the rule is broken
public class Account {
double balance = 100;
void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException("Tried to withdraw " + amount + " but balance is " + balance);
}
balance = balance - amount;
System.out.println("Withdrew " + amount + ", balance now " + balance);
}
}Note: Output:
(No output yet — this defines our own exception type and a method that uses it. Extending Exception makes it checked, so withdraw declares throws InsufficientFundsException. We catch it next.)
Now we put it to work. Because InsufficientFundsException is checked, the calling code must wrap withdraw in a try / catch — exactly as you would for any built-in checked exception. Here main makes one valid withdrawal and one that breaks the rule, then catches the result:
public class Main {
public static void main(String[] args) {
Account acc = new Account();
try {
acc.withdraw(30); // fine
acc.withdraw(500); // too much — throws our custom exception
} catch (InsufficientFundsException e) {
System.out.println("Refused: " + e.getMessage());
}
}
}Note: Output:
Withdrew 30.0, balance now 70.0
Refused: Tried to withdraw 500.0 but balance is 70.0
The first withdrawal worked. The second broke the rule, so withdraw threw our InsufficientFundsException, and the catch printed its message. A named, custom exception makes the problem crystal clear to anyone reading the code or the logs.
The full toolkit at a glance
| Keyword | Role |
|---|---|
try | Wrap code that might fail |
catch | Handle an exception of a given type |
finally | Always run (clean-up), error or not |
throw | Raise an exception yourself |
throws | Declare that a method might raise a checked exception |
Tip: A modern shortcut for clean-up is try-with-resources: try (Scanner sc = new Scanner(...)) { ... }. Anything opened in those brackets is closed automatically when the block ends, so you often do not even need a finally for closing resources.
Q. What is guaranteed about a finally block?
✍️ Practice
- Write a method that throws an
IllegalArgumentExceptionif a price is negative, and call it with a bad value inside a try / catch. - Add a
finallyblock to a try / catch that prints "Done" every time, and confirm it runs in both the success and the error case.
🏠 Homework
- Create a custom
InvalidAgeException(extends Exception). Write a method that throws it when an age is below 0 or above 130, then call the method in a try / catch with both a valid and an invalid age.