Exception Handling

Errors happen. Users of your program can enter unexpected input; files on which your program may rely can be missing or corrupted; there might be a bug in your code that causes your program to crash, etc. These and a great number of other problems can all easily occur.

As you are probably aware alread, what normally happens when errors like this occur is that the program will stop and display some error message. Technically, what you are witnessing when this happens is the throwing of an exception.

Consider the execution of the following code:

import java.util.Scanner;

public class MyClass {
    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter two integers and I will divide them! ");
        String userInput = scanner.nextLine();
        scanner.close();
        processInput(userInput);
    }
    
    public static int doDivision(int a, int b) {
        return a / b;
    }
    
    public static void processInput(String userInput) {
        Scanner scanner = new Scanner(userInput);
        int x = scanner.nextInt();
        int y = scanner.nextInt();
        int z = doDivision(x,y);
        System.out.println("Their quotient is " + z);
        scanner.close();
    }
    
}

Here's two sample runs of the above code -- the first running as expected, the second crashes due to division by zero:

$ java MyClass↵
Enter two integers and I will divide them: 6 3↵
Their quotient is 2
$ java MyClass↵
Enter two integers and I will divide them: 6 0↵
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at MyClass.doDivision(MyClass.java:14)
    at MyClass.processInput(MyClass.java:21)
    at MyClass.main(MyClass.java:10)

Throwing an exception is a lot like playing a game of "hot potato". The error above actually happened in the doDivision() method, but that method didn't know what to do with it -- so it threw the exception to the method that called it, processInput(). This method too, didn't know what to do with the exception. As such, processInput() threw the exception even farther "up the call stack" to the main() method which had called it. The main() method didn't know how to fix the problem either, so it passed it off to the Java Virtual Machine (JVM). The JVM can't fix the problem, but unlike the methods before it, the JVM has a protocol for dealing with situations like this. It stops the program and prints everything it knows about the problem, including what type of exception occurred and the entire chain of methods asked to deal with the problem when it occured.

When we are writing a program, such behavior is fine -- it alerts us to a problem and gives us a chance to change the code so it doesn't happen again. However, crashing the program like that is a fairly violent act -- and not one we want to happen to the end user.

The good news is that we can intercept the exception before it gets thrown up the call stack. We do this by putting code that could result in an exception in the try portion of a try-catch block, and code that attempts to address the exception in a reasonable way in the catch portion of the same. Notice, the type of exception we hope to catch (here, an ArithmeticException) must be specified in the catch block as well. There are many, many types of exceptions built into the Java libraries, and custom ones that you can design as well.

The code below will return $a/b$ unless a "divide-by-zero" arithmetic exception occurs -- in which case it will return a $-1$:

    public static int doDivision(int a, int b) {
        try {
            return a / b;
        }
        catch (ArithmeticException e) {
            return -1;   
        }
    }

Here's the result when this is incorporated with the earlier code and run again with inputs $6$ and $0$:

$ java MyClass↵
Enter two integers and I will divide them: 6 0↵
Their quotient is -1 

Yes, no error is now reported by the JVM -- but given that the quotient should be undefined (and not $-1$), this is honestly not much better.

What we really need to do is tell the user that one can't divide by zero. However, such code is really outside of the purview of what doDivision() was intended to do. Instead, a better approach is to go ahead and let the exception be thrown by the doDivision() method and then catch it "higher up the call stack" -- in the processInput() method, which already involves communicating things to the user, as shown.

import java.util.Scanner;

public class MyClass {
    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter two integers and I will divide them! ");
        String userInput = scanner.nextLine();
        scanner.close();
        processInput(userInput);
    }
    
    public static int doDivision(int a, int b) {
        return a / b;
    }
    
    public static void processInput(String userInput) {
        Scanner scanner = new Scanner(userInput);
        int x = scanner.nextInt();
        int y = scanner.nextInt();
        try {
            int z = doDivision(x,y);
            System.out.println("Their quotient is " + z);
        }
        catch (ArithmeticException e) {
            System.out.println("You can't divide by zero!");
        }
        scanner.close();
    }
}

Here's a run of the above code:

$ java MyClass↵
Enter two integers and I will divide them: 6 0↵
You can't divide by zero!

Much better!

If desired, one can add a "finally" block after the "catch" block, that will execute some additional code regardless of whether or not an exception happened. As an example, consider the following modification:

    public static void processInput(String userInput) {
        Scanner scanner = new Scanner(userInput);
        int x = scanner.nextInt();
        int y = scanner.nextInt();
        try {
            int z = doDivision(x,y);
            System.out.println("Their quotient is " + z);
        }
        catch (ArithmeticException e) {
            System.out.println("You can't divide by zero!");
        }
        finally {
            System.out.println("I have spoken!");
        }
        scanner.close();
    }

Then, both of the following scenarios end with printing "I have spoken!", as shown.

$ java MyClass↵
Enter two integers and I will divide them! 6 2
Their quotient is 3
I have spoken!
$ java MyClass↵
Enter two integers and I will divide them: 6 0↵
You can't divide by zero!
I have spoken!

Throwing Your Own Exceptions

Before one catches an exception, some code somewhere must throw one of course. You can throw your own using the the throw keyword, as the code below demonstrates.

import java.util.Scanner;

public class Jeopardy {
    
    public static String playerName;
    
    public static void main(String[] args) {
        readClue();
        callOnPlayerToRespond(playerName);
        getAndCheckResponse();
    }
    
    public static void readClue() {
        System.out.println("This object-oriented programming language is also" +
                           " an island in Indonesia.");
    }
    
    public static void callOnPlayerToRespond(String name) {
        if (name == null) {
            throw new NullPointerException();
        }
        System.out.println(name + ", your response?");
    }    

    ⋮  
}

The NullPointerException above is an example of an unchecked exception.

RuntimeException and all of its subclasses are unchecked, while Exception and all of its subclasses (with the exception of RuntimeException) are checked exceptions.

What's the difference between a checked and an unchecked exception, you ask? Let's add the getAndCheckResponse() method to the code above, which throws a custom checked exception when the response is not in the form of a question, to find out.

import java.util.Scanner;

public class Jeopardy {
   
    public static class NotInQuestionFormException extends Exception {
        int round;
        
        public NotInQuestionFormException(int round) {
            this.round = round;
        }
        
        public String getMessage() {
            return ("Forgot to respond in the form of a question in round " + round);
        }
    }
    
    public static String playerName = "Bob";
    public static int round = 1;
    
    public static void main(String[] args) {
        readClue();
        callOnPlayerToRespond(playerName);
        try {
            getAndCheckResponse();
        }
        catch (NotInQuestionFormException e) {
            if (e.round == 1) {
                System.out.println("Remember, your response must be in" +
                                   " the form of a question!");
            }
            else {
                System.out.println("I'm sorry, that is incorrect.");
            }
        }
    }
    
    public static void readClue() {
        System.out.println("This object-oriented programming language is also" +
                           " an island in Indonesia.");
    }
    
    public static void callOnPlayerToRespond(String name) {
        if (name == null) {
            throw new NullPointerException();
        }
        System.out.print(name + ", your response? ");
    }
    
    public static void getAndCheckResponse() throws NotInQuestionFormException {
        Scanner scanner = new Scanner(System.in); 
        String response = scanner.nextLine();
        scanner.close();
        if (! response.endsWith("?")) 
             throw new NotInQuestionFormException(round);
        if (response.equals("What is Java?")) System.out.println("Correct!");
        else System.out.println("I'm sorry, that is incorrect.");
    }
}

There are a couple of important things going on in the code above.

Note that we have created our own custom subclass of Exception called NotInQuestionFormException (see the green text above). Since this class is not a subclass of RuntimeException it is a checked exception.

Checked exceptions require that any method that throws one of them up the call stack must declare that it does so. Note that we have a NotInQuestionFormatException thrown in the getAndCheckResponse() method (shown in red), and we declare this possibility (in purple) in that method's signature.

Unchecked exceptions do not have this requirement. Methods may throw unchecked exceptions without stating they do so in their method signatures. Note that we did not add anything to the callOnPlayerToRespond() method signature, but it still throws an (unchecked) NullPointerException.

When deciding whether to make your exceptions thrown checked or unchecked, realize that those who call a method must know about the exceptions that a method can throw so that they can decide what to do about them -- presuming they can do anything at all. Consequently, if a client can reasonably be expected to recover from an exception, it should be made a checked exception -- while if the client cannot do anything to recover from the exception, it should be made an unchecked exception.

Lastly, note that exceptions thrown are actually objects themselves. Consequently, they can store in their instance variables additional information about the precise nature of the exception thrown. In the example above, we store the round information, as the host of Jeopardy responds to such a situation differently depending on which round it is. We access this information through the NotInQuestionFormxception e that is caught in the catch block (in blue).