Progress10 of 14 topics

71% complete

Exception Handling in Java

Exception handling is a powerful mechanism in Java that helps you deal with runtime errors gracefully. It allows your program to detect and handle errors without abruptly terminating, making your applications more robust and user-friendly.

What is an Exception?

In programming, an exception is an event that disrupts the normal flow of program execution. When an error occurs within a method, the method creates an exception object and hands it to the runtime system. This process is called throwing an exception.

Think of exceptions like unexpected situations in real life:

  • Trying to open a file that doesn't exist
  • Attempting to divide a number by zero
  • Running out of memory while your program is executing
  • Trying to access an array element that doesn't exist

Why We Need Exception Handling

Without exception handling, even a minor error can cause your entire program to crash. Exception handling allows you to:
• Separate error-handling code from regular code
• Propagate errors up the call stack
• Group and differentiate different types of errors
• Handle errors appropriately based on their type

Exception Hierarchy in Java

Java exceptions are organized in a class hierarchy, with the Throwable class at the top. All exceptions in Java are derived from this class.

TypeDescriptionExamples
Checked ExceptionsExceptions that must be handled explicitly or declared in method signatureIOException, SQLException, ClassNotFoundException
Unchecked Exceptions (RuntimeExceptions)Exceptions that don't need to be explicitly handled or declaredArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException
ErrorsSerious problems that typically cannot be handled by the applicationOutOfMemoryError, StackOverflowError, VirtualMachineError

Here's a simplified view of the Java exception hierarchy:

Throwable
├── Error                     (serious problems, not typically caught)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── ...
└── Exception                 (base for all exceptions)
    ├── IOException           (checked)
    ├── SQLException          (checked)
    ├── ClassNotFoundException (checked)
    ├── ... 
    └── RuntimeException      (unchecked)
        ├── ArithmeticException
        ├── NullPointerException
        ├── IndexOutOfBoundsException
        └── ...

Basic Exception Handling: try-catch

The most basic form of exception handling in Java uses the try-catch block:

java
1// Basic try-catch structure
2try {
3 // Code that might throw an exception
4} catch (ExceptionType e) {
5 // Code to handle the exception
6}

Let's look at a simple example where we handle a potential division by zero error:

java
1public class DivisionExample {
2 public static void main(String[] args) {
3 int a = 10;
4 int b = 0;
5
6 // Without exception handling - will crash the program
7 // int result = a / b; // ArithmeticException: / by zero
8
9 // With exception handling
10 try {
11 System.out.println("Attempting to divide " + a + " by " + b);
12 int result = a / b;
13 System.out.println("Result: " + result); // This line won't execute if there's an exception
14 } catch (ArithmeticException e) {
15 System.out.println("Error: Cannot divide by zero");
16 System.out.println("Exception message: " + e.getMessage());
17 }
18
19 System.out.println("Program continues execution...");
20 }
21}

When you run this program, you'll see:

Attempting to divide 10 by 0
Error: Cannot divide by zero
Exception message: / by zero
Program continues execution...

Notice that the program didn't crash - it caught the exception, handled it, and continued execution.

Multiple catch Blocks

You can have multiple catch blocks to handle different types of exceptions:

java
1import java.io.FileInputStream;
2import java.io.FileNotFoundException;
3import java.io.IOException;
4
5public class MultipleCatchExample {
6 public static void main(String[] args) {
7 try {
8 // This might throw FileNotFoundException
9 FileInputStream file = new FileInputStream("nonexistent.txt");
10
11 // This might throw IOException
12 int data = file.read();
13
14 // This might throw ArithmeticException
15 int result = 10 / 0;
16
17 } catch (FileNotFoundException e) {
18 System.out.println("Error: File not found");
19 System.out.println("Exception: " + e.getMessage());
20 } catch (IOException e) {
21 System.out.println("Error: Problem reading the file");
22 System.out.println("Exception: " + e.getMessage());
23 } catch (ArithmeticException e) {
24 System.out.println("Error: Arithmetic problem");
25 System.out.println("Exception: " + e.getMessage());
26 }
27
28 System.out.println("Program continues execution...");
29 }
30}

Order of Catch Blocks

When using multiple catch blocks, the order matters. You must arrange them from most specific to most general exception types. Java will check them in order and execute the first one that matches.

If you place a parent exception type before its child types, the child catch blocks will never be reached (and you'll get a compilation error).

Multi-catch (Java 7+)

In Java 7 and later, you can catch multiple exception types in a single catch block:

java
1import java.io.FileInputStream;
2import java.io.FileNotFoundException;
3import java.io.IOException;
4
5public class MultiCatchExample {
6 public static void main(String[] args) {
7 try {
8 // Code that might throw exceptions
9 FileInputStream file = new FileInputStream("file.txt");
10 int data = file.read();
11
12 } catch (FileNotFoundException | NullPointerException e) {
13 // Handle either exception the same way
14 System.out.println("File error or null reference: " + e.getMessage());
15 } catch (IOException e) {
16 System.out.println("IO error: " + e.getMessage());
17 }
18 }
19}

The finally Block

The finally block contains code that will execute regardless of whether an exception is thrown or caught. It's typically used for cleanup operations like closing files, network connections, or database connections.

java
1import java.io.FileInputStream;
2import java.io.FileNotFoundException;
3import java.io.IOException;
4
5public class FinallyExample {
6 public static void main(String[] args) {
7 FileInputStream file = null;
8
9 try {
10 file = new FileInputStream("example.txt");
11 // Read operations...
12
13 } catch (FileNotFoundException e) {
14 System.out.println("File not found: " + e.getMessage());
15 } catch (IOException e) {
16 System.out.println("Error reading file: " + e.getMessage());
17 } finally {
18 // This code always executes, even if an exception occurs
19 System.out.println("Executing finally block");
20
21 // Proper resource cleanup
22 if (file != null) {
23 try {
24 file.close();
25 System.out.println("File closed successfully");
26 } catch (IOException e) {
27 System.out.println("Error closing file: " + e.getMessage());
28 }
29 }
30 }
31
32 System.out.println("Program continues...");
33 }
34}

When to Use finally

The finally block is ideal for cleanup code that should execute regardless of whether an exception occurred:

  • Closing file streams
  • Closing database connections
  • Releasing network resources
  • Releasing locks or other system resources

Try-with-Resources (Java 7+)

Java 7 introduced the try-with-resources statement, which automatically closes resources that implement the AutoCloseable or Closeable interface. This is much cleaner than using finally blocks.

java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.IOException;
4
5public class TryWithResourcesExample {
6 public static void main(String[] args) {
7 // Old way (pre-Java 7)
8 BufferedReader oldReader = null;
9 try {
10 oldReader = new BufferedReader(new FileReader("file.txt"));
11 String line = oldReader.readLine();
12 System.out.println(line);
13 } catch (IOException e) {
14 System.out.println("Error: " + e.getMessage());
15 } finally {
16 if (oldReader != null) {
17 try {
18 oldReader.close();
19 } catch (IOException e) {
20 System.out.println("Error closing reader: " + e.getMessage());
21 }
22 }
23 }
24
25 // New way with try-with-resources (Java 7+)
26 try (BufferedReader newReader = new BufferedReader(new FileReader("file.txt"))) {
27 String line = newReader.readLine();
28 System.out.println(line);
29 } catch (IOException e) {
30 System.out.println("Error: " + e.getMessage());
31 }
32 // No need for finally block - resources are automatically closed
33 }
34}

The try-with-resources statement automatically calls close() on the resources when the try block exits, whether normally or due to an exception.

Multiple Resources

You can manage multiple resources in a single try-with-resources statement:

java
1import java.io.*;
2
3public class MultipleResourcesExample {
4 public static void main(String[] args) {
5 try (
6 FileInputStream input = new FileInputStream("input.txt");
7 FileOutputStream output = new FileOutputStream("output.txt")
8 ) {
9 // Read from input and write to output
10 int data;
11 while ((data = input.read()) != -1) {
12 output.write(data);
13 }
14 System.out.println("Data copied successfully");
15 } catch (IOException e) {
16 System.out.println("Error: " + e.getMessage());
17 }
18 // Both resources are automatically closed
19 }
20}

Throwing Exceptions

So far, we've discussed catching exceptions. But you can also throw exceptions explicitly using the throw keyword.

java
1public class ThrowExample {
2 public static void main(String[] args) {
3 try {
4 validateAge(15); // This is fine
5 validateAge(-5); // This will throw an exception
6 } catch (IllegalArgumentException e) {
7 System.out.println("Validation Error: " + e.getMessage());
8 }
9 }
10
11 public static void validateAge(int age) {
12 if (age < 0) {
13 throw new IllegalArgumentException("Age cannot be negative");
14 }
15 System.out.println("Age is valid: " + age);
16 }
17}

Declaring Exceptions with throws

When a method might throw a checked exception that it doesn't handle, you must declare it using the throws keyword:

java
1import java.io.FileReader;
2import java.io.IOException;
3
4public class ThrowsExample {
5 public static void main(String[] args) {
6 try {
7 readFile("example.txt");
8 } catch (IOException e) {
9 System.out.println("Error reading file: " + e.getMessage());
10 }
11 }
12
13 // This method declares that it might throw an IOException
14 public static void readFile(String fileName) throws IOException {
15 FileReader reader = new FileReader(fileName);
16 int data = reader.read();
17 while (data != -1) {
18 System.out.print((char) data);
19 data = reader.read();
20 }
21 reader.close();
22 }
23}

throws vs. throw

Don't confuse throws with throw:

  • throws - Used in method declaration to indicate what exceptions the method might throw
  • throw - Used to actually throw an exception at runtime

Creating Custom Exceptions

Java allows you to create your own exception classes by extending existing exception classes. This is useful for creating application-specific exceptions.

Step 1: Define the Custom Exception

java
1// Custom checked exception
2public class InsufficientFundsException extends Exception {
3 private double amount;
4
5 public InsufficientFundsException(double amount) {
6 super("Insufficient funds: Shortage of $" + amount);
7 this.amount = amount;
8 }
9
10 public double getAmount() {
11 return amount;
12 }
13}

Step 2: Use the Custom Exception

java
1public class BankAccount {
2 private String accountNumber;
3 private double balance;
4
5 public BankAccount(String accountNumber, double initialBalance) {
6 this.accountNumber = accountNumber;
7 this.balance = initialBalance;
8 }
9
10 public void withdraw(double amount) throws InsufficientFundsException {
11 if (amount <= 0) {
12 throw new IllegalArgumentException("Withdrawal amount must be positive");
13 }
14
15 if (amount > balance) {
16 double shortage = amount - balance;
17 throw new InsufficientFundsException(shortage);
18 }
19
20 balance -= amount;
21 System.out.println("Withdrawal successful. New balance: $" + balance);
22 }
23
24 public String getAccountNumber() {
25 return accountNumber;
26 }
27
28 public double getBalance() {
29 return balance;
30 }
31}
32
33public class BankDemo {
34 public static void main(String[] args) {
35 BankAccount account = new BankAccount("12345", 1000.00);
36
37 try {
38 System.out.println("Withdrawing $500...");
39 account.withdraw(500.00);
40
41 System.out.println("Withdrawing $600...");
42 account.withdraw(600.00); // This should cause an exception
43 } catch (InsufficientFundsException e) {
44 System.out.println(e.getMessage());
45 System.out.println("You need $" + e.getAmount() + " more to complete this transaction");
46 }
47 }
48}

Custom exceptions improve code readability and make your error handling more specific to your application domain.

Exception Handling Best Practices

Do

  • Only catch exceptions you can handle
  • Use specific exception types when possible
  • Log exceptions with meaningful information
  • Use try-with-resources for automatic resource cleanup
  • Create custom exceptions for application-specific errors
  • Clean up resources in finally blocks (if not using try-with-resources)

Don't

  • Catch exceptions you can't handle properly
  • Use empty catch blocks (swallowing exceptions)
  • Catch Exception or Throwable without a good reason
  • Use exceptions for normal flow control
  • Throw exceptions from finally blocks
  • Ignore checked exceptions without documentation

Common Exception Handling Patterns

PatternUse Case
Log and RethrowLog the exception details and then rethrow it for higher-level handling
Translate ExceptionCatch a lower-level exception and throw a higher-level, more appropriate one
Fail-FastValidate inputs immediately and throw exceptions early
Retry LogicCatch an exception, wait briefly, and retry the operation

Common Java Exceptions

ExceptionDescriptionCommon Cause
NullPointerExceptionThrown when a null reference is used where an object is requiredTrying to call a method on a null object reference
ArrayIndexOutOfBoundsExceptionThrown when trying to access an array element with an invalid indexUsing negative index or index larger than array length - 1
ClassCastExceptionThrown when trying to cast an object to a subclass it is not an instance ofIncorrect type casting
IllegalArgumentExceptionThrown when a method receives an argument that's inappropriatePassing a negative value to a method that requires positive values
IOExceptionThrown when an I/O operation failsFile not found, disk full, network connection lost
SQLExceptionThrown when a database access error occursInvalid SQL, database connection lost, authentication failed

Advanced Exception Handling

Chained Exceptions

Java allows you to chain exceptions to indicate that one exception was caused by another:

java
1import java.io.IOException;
2import java.sql.SQLException;
3
4public class ChainedExceptionExample {
5 public static void main(String[] args) {
6 try {
7 processFile("data.txt");
8 } catch (IOException e) {
9 System.out.println("Main error: " + e.getMessage());
10
11 // Get the cause
12 Throwable cause = e.getCause();
13 if (cause != null) {
14 System.out.println("Caused by: " + cause.getMessage());
15 }
16 }
17 }
18
19 public static void processFile(String filename) throws IOException {
20 try {
21 readDatabaseFile(filename);
22 } catch (SQLException e) {
23 // Wrap the SQLException in an IOException
24 throw new IOException("Could not process file " + filename, e);
25 }
26 }
27
28 public static void readDatabaseFile(String filename) throws SQLException {
29 // Simulate a database error
30 throw new SQLException("Database connection lost while reading " + filename);
31 }
32}

Suppressed Exceptions

In try-with-resources, if both the try block and the resource closing throw exceptions, the exception from the try block is thrown and the exception from closing is suppressed. You can access these suppressed exceptions:

java
1import java.io.FileInputStream;
2import java.io.IOException;
3
4class CustomResource implements AutoCloseable {
5 private String name;
6
7 public CustomResource(String name) {
8 this.name = name;
9 System.out.println("Resource " + name + " created");
10 }
11
12 public void process() throws IOException {
13 System.out.println("Processing resource " + name);
14 throw new IOException("Error processing resource " + name);
15 }
16
17 @Override
18 public void close() throws Exception {
19 System.out.println("Closing resource " + name);
20 throw new Exception("Error closing resource " + name);
21 }
22}
23
24public class SuppressedExceptionExample {
25 public static void main(String[] args) {
26 try (CustomResource resource = new CustomResource("test")) {
27 resource.process();
28 } catch (Exception e) {
29 System.out.println("Main exception: " + e.getMessage());
30
31 // Get suppressed exceptions
32 Throwable[] suppressed = e.getSuppressed();
33 for (Throwable t : suppressed) {
34 System.out.println("Suppressed: " + t.getMessage());
35 }
36 }
37 }
38}

Assertions

Java's assert statement provides a way to check assumptions during development:

java
1public class AssertionExample {
2 public static void main(String[] args) {
3 int value = -5;
4
5 // Assert that value is positive
6 assert value >= 0 : "Value must be positive, but was: " + value;
7
8 System.out.println("Value: " + value);
9 }
10}

Note on Assertions

Assertions are disabled by default in Java. To enable them, use the -ea or -enableassertions flag:
java -ea AssertionExample

Assertions should only be used for debugging and not for regular error checking in production code.

Summary

Exception handling in Java allows you to:

  • Detect and handle runtime errors gracefully
  • Separate error-handling code from regular code
  • Create a more robust application that doesn't crash when encountering errors
  • Provide better user experience by reporting errors in a user-friendly way
  • Ensure proper resource cleanup even when errors occur

By mastering exception handling in Java, you'll create more reliable, maintainable, and user-friendly applications.

Related Tutorials

Learn how to work with files in Java.

Learn more

Understand Java collections framework.

Learn more

Master the principles of OOP in Java.

Learn more