3.8. Explicitly Throwing Exceptions & Exception Propagation

It may seem counterintuitive but sometimes you will need to write code to instantiate and/or throw (propagate) exception objects instead of catching them. Remember, exceptions are used to indicate that something unexpected happened. They can be both informative and useful - not only to us when we use existing code but also when we write code that others will use.

Often times in this class, you will be implementing methods that others will call. If they pass in an invalid value (parameter/argument), it is better to throw an exception (and let them catch it) rather than printing a message to the screen. Throwing an exception allows them to recover from the mistake and avoids unnecessary (and sometimes confusing) output.

In this section, we will see how you can throw exceptions from code that you have written and why it may be better to throw an exception instead of handling them in certain scenarios.

Example 1: Instantiating and Throwing an Unchecked Exception

In Java, the throw keyword is used to explicitly throw an exception. When a throw statement is encountered within a method, that method immediately stops its execution and throws a reference to an exception object (often containing a message about what went wrong) to the calling method.

Don’t confuse returning a value with throwing an exception. A method in Java can either return a value (if there is no exception) or throw an exception.

We will use the code below to demonstrate how you might throw an unchecked exception to a calling method. Take a few minutes to read through the code before reading on. Write down what you think will happen in your notes.

 1public class ThrowException {
 2
 3   public static void main(String[] args) {
 4      double[] values = new double[0]; // create an array of size 0.
 5      double average = ThrowException.computeAverage(values);
 6      System.out.println("The average of the values in the array is: " + average);
 7   } // main
 8
 9   public static double computeAverage(double[] nums) {
10      if (nums.length == 0) {
11        throw new IllegalArgumentException("nums array cannot be empty");
12      } // if
13      double sum = 0;
14      for (double num : nums) {
15         sum += num;
16      } // for
17      return sum / nums.length;
18   } // computeAverage
19} // ThrowException

Since the array referenced by values is created with a size of 0, the computeAverage method will create a new IllegalArgumentException and throw a reference to that newly created object back to main. Note that in this scenario, the computeAverage method does not return a value.

Important

It is not appropriate for the computeAverage method to handle the exception in this scenario. Instead, it should be up to the calling method to handle the exception since that is where the array is created. Remember, the computeAverage method could be called by a method other than main (even a method in a separate class) and could be called under a variety of input scenarios. For example, the values in the nums array could be provided by the user or they could be read from a file. In the first scenario, the calling method may want to respond by asking the user for new (or additional values). In the second scenario, the response to the exception may be to ask for a file that contains more values. If we were to code the exception handling in the computeAverage method, we could only do one of these things which would limit the reusability of the method. From the perspective of computeAverage, it shouldn’t matter if the values come from the user or from a file. The job for the programmer of computeAverage should simply be to alert the caller that something went wrong and they can do this by throwing an exception and letting the caller deal with the exception in an appropriate way.

Example 2: Propagating an Unchecked Exception Created Elsewhere

In this example, we will allow an exception created in another method to propagate to the caller. Here, we don’t have to explicitly throw the exception. Implicitly, we propagate (throw) the exception simply by intentionally choosing not to catch it.

 1public class PropagateException {
 2
 3   public static void main(String[] args) {
 4      double[] values = new double[0]; // create an array of size 0.
 5      double max = PropagateException.computeMax(values);
 6      System.out.println("The maximum value of the array is: " + max);
 7   } // main
 8
 9   public static double computeMax(double[] nums) {
10
11      // This line causes an ArrayIndexOutOfBoundsException
12      double max = nums[0];
13
14      for (double num : nums) {
15         if (num > max) {
16            max = num;
17         } // if
18      } // for
19      return max;
20   } // computeMax
21} // PropagateException

This example works similar to example 1 in that it will crash when we run it. However, we did not have to instantiate the exception object or explicitly use throw. The exception object is still created and it is still thrown. However, the throw happens internally when Java tries to access the first element of an empty array on line 12 of the code.

The exception object is created on line 12, and because it isn’t caught inside of computeMax, it automatically propagates to the calling method main. Since main doesn’t catch it, the exception propagates outside of main and crashes the program.

Test Yourself

Take a few minutes to compare the two examples above. Why did the first example require us to instantiate an object and throw it where the second example did not?

Now, attempt to compile example 2 on Odin. Read the error message you receive and make sure you understand it. Then, catch the exception in main and make sure you recover by printing an appropriate error message to the user instead of crashing.

Example 3: Throwing/Propagating a Checked Exception

We can instantiate and throw both checked and unchecked exceptions. However, there is one important difference when you throw or propagate a checked exception. In these cases, you need to add a throws clause to the method signature to tell the compiler that it is your intent to propagate the exception. Remember, the compiler forces us to deal with checked exceptions in one of two ways. If we choose not to catch them, we must explicitly propagate them using throws.

In the example below, we show how this can be done:

 1import java.io.File;
 2import java.io.FileNotFoundException;
 3import java.util.Scanner;
 4
 5public class PropagateChecked {
 6
 7   public static void main(String[] args) {
 8      String line = PropagateChecked.firstLine("notes.txt");
 9      System.out.println("The first line is: " + line);
10   } // main
11
12   public static String firstLine(String filename) throws FileNotFoundException {
13      File file = new File(filename);
14
15      // Throws a FileNotFoundException if notes.txt doesn't exist
16      Scanner input = new Scanner(file);
17
18      String first = input.nextLine();
19      return first;
20   } // firstLine
21} // PropagateChecked

Since the firstLine method is known to throw an exception, we would usually want to handle the exception by placing the code in a try block. However, in this scenario, it is better to propagate the exception to the caller so they can handle the exception. Notice that because FileNotFoundException is a checked exception we have to explicitly propagate it by adding the throws keyword in the signature of the method.

Using the throws keyword, we told Java that the FileNotFoundException will not be handled directly in this method. Instead, it will be propagated up to the calling method, i.e., the method (or methods), somewhere else, that is calling firstLine. In that other method, the programmer can either handle the exception (using a try-catch) or choose to propagate it again by repeating the throws in the calling method’s signature.

Important

In Java, checked exceptions must either be handled directly using a try-catch or propagated up using throws. Note that while it is possible to place a throws in the signature of a program’s main method, doing so is strongly discouraged as exceptions propagated past main will always cause the program to crash.

Important

Understanding the difference between throw, throws, and @throws:

  • The throw keyword is used in a block of code to explicitly throw an exception. This is desirable when you want your method to throw an exception under some predefined conditions. If the exception object being thrown using throw is a checked exception, then you might also need to include throws in the method signature.

  • The throws keyword is used in a method or constructor signature to list the checked exceptions that the method is allowed to propagate.

  • The @throws tag is a Javadoc tag that is used in the Javadoc comment associated with a method (or constructor) to document that it can throw an exception under certain circumstances. The rule of thumb is this: if your method throws an exception (checked or unchecked) that a user of your method should handle, then you should document that exception using @throws in the associated Javadoc comment.

Rapid Fire Review
  1. Which keyword is used to explicitly throw an exception?

    1. catch

    2. return

    3. throw

    4. propagate

    5. throws

  2. Which keyword is used to indicate that a method might throw a checked exception and that our intent is to propagate that exception if it occurs?

    1. catch

    2. return

    3. throw

    4. propagate

    5. throws

  3. Why might it be better to throw an exception rather than handle it within a particular method?

    1. To ensure the method returns a value.

    2. To allow the calling method to handle the exception in different ways.

    3. To prevent the method from crashing.

    4. To make the method shorter.

    5. To pass work to someone else because we don’t have time to do it.

  4. What happens when a method encounters a throw statement?

    1. The method continues execution and prints an error message.

    2. The method stops execution and returns a default value.

    3. The method stops execution and throws an exception to the calling method.

    4. The method retries the operation.

    5. The method asks the user for better input.

  5. Why doesn’t the computeMax method need to explicitly throw an exception when accessing the first element of an empty array?

    1. the exception is caught inside computeMax.

    2. the exception is handled by the Java compiler.

    3. the exception is implicitly thrown.

    4. the method is protected against errors by default.

    5. there is no exception caused by computeMax.