- Master the syntax and usage of try-catch blocks
- Learn how to catch multiple exception types
- Understand the finally block and its use cases
- Learn how to throw custom exceptions
- Understand exception propagation and the throws keyword
Try-Catch Blocks and Exception Types
You know what exceptions are. Now you need to catch them before they crash your program.
The Try-Catch Block: Your Safety Net
The try-catch block is Java's fundamental mechanism for handling exceptions. Think of it as a safety net that catches errors before they bring everything down.
Basic Syntax
try {
// Code that might throw an exception
int result = 10 / 0;
} catch (ArithmeticException e) {
// Code to handle the exception
System.out.println("Error: Cannot divide by zero!");
}
System.out.println("Program continues normally...");
How it works:
- Java tries to execute code in the
tryblock - If an exception occurs, execution immediately jumps to the matching
catchblock - The
catchblock handles the error - Program continues after the try-catch
Real Example: User Input Validation
Scanner scanner = new Scanner(System.in);
System.out.print("Enter a number: ");
try {
int number = scanner.nextInt();
System.out.println("You entered: " + number);
int result = 100 / number;
System.out.println("100 divided by " + number + " = " + result);
} catch (InputMismatchException e) {
System.out.println("Error: Please enter a valid integer!");
} catch (ArithmeticException e) {
System.out.println("Error: Cannot divide by zero!");
}
System.out.println("Thank you for using the calculator!");
Output Examples:
Valid input (5):
Enter a number: 5
You entered: 5
100 divided by 5 = 20
Thank you for using the calculator!
Invalid input (abc):
Enter a number: abc
Error: Please enter a valid integer!
Thank you for using the calculator!
Zero input:
Enter a number: 0
You entered: 0
Error: Cannot divide by zero!
Thank you for using the calculator!
Notice the program doesn't crash - it handles errors gracefully!
Multiple Catch Blocks
You can catch different exception types and handle them differently:
public void readAndParseFile(String filename) {
try {
File file = new File(filename);
Scanner scanner = new Scanner(file);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
int value = Integer.parseInt(line);
System.out.println("Parsed: " + value);
}
scanner.close();
} catch (FileNotFoundException e) {
System.out.println("Error: File '" + filename + "' not found.");
System.out.println("Please check the filename and try again.");
} catch (NumberFormatException e) {
System.out.println("Error: File contains non-numeric data.");
System.out.println("Line: " + e.getMessage());
} catch (Exception e) {
System.out.println("An unexpected error occurred: " + e.getMessage());
e.printStackTrace();
}
}
Order matters! Always catch specific exceptions before general ones:
// GOOD - Specific to general
try {
riskyCode();
} catch (FileNotFoundException e) { // Most specific
// Handle file not found
} catch (IOException e) { // More general
// Handle other I/O errors
} catch (Exception e) { // Most general
// Handle any other exception
}
// BAD - Won't compile!
try {
riskyCode();
} catch (Exception e) { // Too general, catches everything
// Handle exception
} catch (IOException e) { // Never reached! Compilation error!
// This code is unreachable
}
Multi-Catch: Handling Multiple Exceptions the Same Way
Java 7+ lets you catch multiple exception types in one catch block:
try {
String data = readFromNetwork();
int value = Integer.parseInt(data);
saveToDatabase(value);
} catch (IOException | NumberFormatException e) {
// Handle both exceptions the same way
System.out.println("Error processing data: " + e.getMessage());
logError(e);
}
Benefits:
- Less code duplication
- Cleaner and more readable
- Same handling for related exceptions
Rules:
- Exception types separated by pipe
| - Can't catch exception and its subclass together
- The exception variable is implicitly
final
The Finally Block: Always Execute
The finally block always executes, whether an exception occurs or not. It's perfect for cleanup operations.
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("Error reading file: " + e.getMessage());
} finally {
// This ALWAYS runs!
if (reader != null) {
try {
reader.close();
System.out.println("File closed successfully");
} catch (IOException e) {
System.out.println("Error closing file");
}
}
}
When does finally execute?
- ✅ When no exception occurs
- ✅ When exception is caught
- ✅ When exception is not caught (before propagating)
- ✅ Even if there's a return statement in try or catch
- ❌ Only if JVM exits (System.exit()) or thread dies
Finally with Return Statements
public int demonstrateFinally() {
try {
return 1; // Would return here...
} finally {
System.out.println("Finally executes even with return!");
// Finally runs before method actually returns!
}
}
Output:
Finally executes even with return!
Then returns 1.
Try-With-Resources: Automatic Cleanup (Java 7+)
A modern, cleaner way to handle resources that need closing:
// Old way - verbose
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// New way - clean and automatic!
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
// reader.close() is called automatically!
Benefits:
- Resources automatically closed
- Less boilerplate code
- Prevents resource leaks
- Multiple resources supported
// Multiple resources
try (FileReader fr = new FileReader("input.txt");
BufferedReader br = new BufferedReader(fr);
FileWriter fw = new FileWriter("output.txt")) {
String line;
while ((line = br.readLine()) != null) {
fw.write(line + "\n");
}
// All three resources closed automatically!
}
Throwing Exceptions
Sometimes you need to throw exceptions yourself:
Using 'throw'
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException(
"Age must be between 0 and 150, got: " + age
);
}
this.age = age;
}
Custom Exception Messages
public void withdraw(double amount) {
if (amount > balance) {
throw new IllegalStateException(
"Insufficient funds. Balance: $" + balance +
", Requested: $" + amount
);
}
balance -= amount;
}
The 'throws' Keyword: Declaring Exceptions
When you don't want to handle an exception in a method, declare it with throws:
// This method declares it might throw IOException
public String readFile(String filename) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(filename));
String content = reader.readLine();
reader.close();
return content;
}
// Caller must handle it
public void processFile() {
try {
String data = readFile("data.txt");
System.out.println(data);
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
}
Multiple exceptions:
public void complexOperation() throws IOException, SQLException {
// Method that might throw both exceptions
}
Difference: throw vs throws
throw- Actually throws an exception (used in method body)throws- Declares that a method might throw exceptions (in signature)
public void example() throws IOException { // throws - declares
if (someCondition) {
throw new IOException("Error!"); // throw - actually throws
}
}
Exception Propagation
When an exception isn't caught, it propagates up the call stack:
public void method1() {
method2();
}
public void method2() {
method3();
}
public void method3() {
throw new RuntimeException("Error in method3!");
}
// Call chain: method1() → method2() → method3() → Exception!
// Exception propagates: method3() → method2() → method1() → caller
Example with handling:
public void method1() {
try {
method2();
} catch (RuntimeException e) {
System.out.println("Caught in method1: " + e.getMessage());
}
}
public void method2() {
method3(); // Exception propagates from here
}
public void method3() {
throw new RuntimeException("Error!");
}
Creating Custom Exceptions
For application-specific errors, create custom exception classes:
// Custom checked exception
public class InsufficientFundsException extends Exception {
private double deficit;
public InsufficientFundsException(double deficit) {
super("Insufficient funds. Deficit: $" + deficit);
this.deficit = deficit;
}
public double getDeficit() {
return deficit;
}
}
// Usage
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
double deficit = amount - balance;
throw new InsufficientFundsException(deficit);
}
balance -= amount;
}
// Handling
try {
account.withdraw(1000);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage());
System.out.println("You need $" + e.getDeficit() + " more.");
}
Best Practices
1. Catch Specific Exceptions
// GOOD
try {
code();
} catch (FileNotFoundException e) {
// Handle missing file
} catch (IOException e) {
// Handle I/O errors
}
// BAD
try {
code();
} catch (Exception e) {
// Too broad!
}
2. Don't Catch What You Can't Handle
// BAD - catching but not handling
try {
code();
} catch (IOException e) {
// Empty - exception disappears!
}
// GOOD - at least log it
try {
code();
} catch (IOException e) {
logger.error("Failed to process file", e);
throw e; // Re-throw if can't handle
}
3. Provide Meaningful Messages
// BAD
throw new Exception("Error");
// GOOD
throw new FileNotFoundException(
"Configuration file 'config.xml' not found in directory: " + dir
);
4. Clean Up Resources
// GOOD - Use try-with-resources
try (Connection conn = getConnection()) {
// Use connection
}
// Or use finally
Connection conn = null;
try {
conn = getConnection();
// Use connection
} finally {
if (conn != null) conn.close();
}
Key Takeaways
- try-catch blocks handle exceptions and prevent crashes
- Multiple catch blocks handle different exceptions differently
- Multi-catch handles multiple exceptions the same way (Java 7+)
- finally always executes - perfect for cleanup
- try-with-resources automatically closes resources
- throw creates and throws an exception
- throws declares that a method might throw exceptions
- Exceptions propagate up the call stack until caught
- Custom exceptions provide application-specific error handling
- Always close resources in finally or use try-with-resources
What's Next?
Now that you can handle exceptions like a pro, we'll explore a common source of exceptions: file input/output! In the next lesson, you'll learn:
- How to read from and write to files
- Different ways to handle file paths
- Working with text and binary files
- Best practices for file operations
Get ready to make your programs interact with the file system!
