Skip to content

Chapter 8 Exception Handling in Java

Exception handling is a crucial component in Java programming. It helps manage program flow and handle unexpected situations gracefully.

Understanding Exceptions

Exceptions are events that occur during the execution of programs that disrupt the normal flow of instructions. An exception can occur for several reasons, including the following:

  • User has entered invalid data.
  • File that needs to be opened cannot be found.
  • Network communication is lost in the middle of communications.
  • Hardware failure.
  • Out of memory and other resource failures.

In Java, exceptions are mainly used for the following reasons:

  • Separating Error Handling Code from "Regular" Code: Exceptions provide the means to separate the details of what to do when something out of the ordinary happens from the main logic of a program. In traditional programming, error detection, reporting, and handling often lead to confusing spaghetti code.

  • Propagating Errors Up the Call Stack: A method can catch an exception it chooses to handle. But if it doesn't handle some exception, the method can pass that exception up the call stack to its caller. This propagation continues until an appropriate catcher is found, potentially as far up as the initial call into the Java system.

  • Grouping and Differentiating Error Types: Because all exceptions in Java are objects, the grouping or categorizing of exceptions is a natural outcome of the class hierarchy. Programs can use different catch clauses for different types of exceptions to differentiate the kinds of errors that can occur.

When an error occurs within a method, the method creates an object and hands it off to the runtime system. The object, called an exception object, contains information about the error, including its type and the state of the program when the error occurred. The exception object contains a lot of debugging information such as method hierarchy, line number where the exception occurred, type of exception, etc.

Here is a sample exception:

java.lang.NullPointerException
    at com.example.myproject.Book.getTitle(Book.java:16)
    at com.example.myproject.Author.getBookTitles(Author.java:25)
    at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

In the Java Exception Hierarchy, there are primarily three types of Throwable objects:

  1. Error: These are exceptional conditions that are external to the application, and that the application usually cannot anticipate or recover from. These are usually at the level of the JVM and indicate something severe that has gone wrong, such as StackOverflowError or OutOfMemoryError.

  2. Exception: Exceptions are a type of throwable that a program can try and catch. The Exception class is used for any type of exception that your program throws and that it can catch. This is the class you need to extend to create your own exceptions. Importantly, Exception includes both checked and unchecked exceptions.

  3. RuntimeException: The RuntimeException is a subclass of Exception. Unlike checked exceptions, runtime exceptions are runtime exceptions, meaning that they can be thrown during the execution of the program. These are also known as unchecked exceptions, because the compiler doesn't require methods to catch them or declare them in the method's throws clause.

Here's how they relate to each other:

java.lang.Object
│
└── java.lang.Throwable
    │
    ├── java.lang.Error
    │
    └── java.lang.Exception
        │
        ├── java.lang.RuntimeException (Unchecked)
        │
        └── Other exceptions (Checked)
  • Checked exceptions: These are exceptional conditions that a well-written application should anticipate and recover from. For example, the FileNotFoundException will be thrown by methods that are unsuccessful in finding a file on disk, and that is something that you as a developer might reasonably expect to happen and to handle gracefully.

  • Unchecked exceptions: These are exceptions that can be thrown during the execution of a method. They include programming bugs, such as logic errors or improper use of an API. RuntimeException is the superclass of those exceptions that can be thrown during the normal operation of the Java Virtual Machine. RuntimeException and its subclasses are unchecked exceptions.

Understanding when and how to use these exceptions is key to writing robust, secure, and maintainable Java code. Your code should throw and catch exceptions to handle error conditions and to communicate problems to the user in a controlled and thoughtful manner.

Using try, catch, finally Blocks

Java uses try, catch, and finally blocks to handle exceptions.

  • try block: The try block contains a set of statements where an exception can occur.

  • catch block: The catch block is used to handle the exception. It follows the try block and can exist without a finally block. There can be multiple catch blocks for a single try block to handle different types of exceptions separately.

  • finally block: The finally block always executes whether an exception is thrown or not, making it useful for cleanup activities, like closing files or releasing resources.

Here is an example:

try {
    // code that may throw an exception
    int result = 10 / 0;
} catch (ArithmeticException e) {
    // handle the exception
    System.out.println("Error: Division by zero is not allowed.");
} finally {
    // cleanup activities
    System.out.println("This is the cleanup stage.");
}

Throwing and Catching Exceptions

In Java, you can also throw exceptions manually using the throw keyword. This is usually done to indicate that something has failed and execution can't continue. The caller method is responsible for handling the thrown exception.

Here is an example of throwing and catching an exception:

public void checkAge(int age) {
    if (age < 18) {
        throw new ArithmeticException("Access denied - You must be at least 18 years old.");
    } else {
        System.out.println("Access granted - You are old enough!");
    }
}

public void verifyAge() {
    try {
        checkAge(15);
    } catch (ArithmeticException e) {
        System.out.println(e.getMessage());
    }
}

In this code, the checkAge method throws an ArithmeticException if the age is less than 18. The verifyAge method then catches and handles the exception.

Remember, exception handling in Java isn't just about fixing a problem. It's about creating a system that's robust and easy to debug. By understanding the importance of exception handling, you'll be able to write more resilient and robust Java code.