Chapter 6.J

Java Exceptions

Exceptions in Java

Subsections of Java Exceptions

Common Exceptions & Errors

Before we learn about how to detect and handle exceptions in Java, let’s review some of the common exceptions and errors we may see in our programs. Each of the headers below is the name of an exception in Java, which is represented by a particular class in the Java programming language.

Exception

Java 8 API Reference

Every exception that can be handled in Java is a subtype of the Exception class. So, when we aren’t sure which type of exception to expect, we can always use the Exception class to make sure we catch all of them.

ArithmeticException

Java 8 API Reference

An ArithmeticException can occur whenever our program attempts to perform a calculation that would result in an undefined value. In Java, this is the case when we divide by 0. So, the following code would generate an ArithmeticException:

int a = 10;
int b = 0;
int c = a / b; // throws ArithmeticException

However, when using floating point values, the result is different:

double a = 5.0;
double b = 0.0;
double c = a / b; // Infinity

In short, this is because the standard for floating point numbers defines Infinity as 1.0 / 0.0, so it is a valid value according to the standard definition for floating point numbers.

ArrayIndexOutOfBoundsException

Java 8 API Reference

An ArrayIndexOutOfBoundsException happens when we try to access an array index that does not exist. Here’s a great example:

int[] array = new int[5];
array[5] = 10; // throws ArrayIndexOutOfBoundsException

In this example, we are trying to access the 6th element in the array, which is at array index 5. However, since the size of the array is only 5, we’ll get an ArrayIndexOutOfBoundsException when we try to execute this code since there is no 6th element.

StringIndexOutOfBoundsException

Java 8 API Reference

Similar to the ArrayIndexOutOfBoundsException above, a StringIndexOutOfBoundsException occurs when we try to access an invalid index of a character in a string. Here’s a couple of examples:

String s = "abc";
char c = s.charAt(3); // throws StringIndexOutOfBoundsException
char d = s.charAt(-1); // throws StringIndexOutOfBoundsException

In this example, we are first trying to access the character at index 3. Just like with arrays, that would be the 4th character in the string. Since the string only contains 3 characters, it would result in an exception. Likewise, we cannot access a negative character index in Java, so it would also cause this exception to be thrown.

FileNotFoundException

Java 8 API Reference

This exception occurs when the program is trying to open a file that does not exist. This is one of the more common errors that programmers must deal with when using files for input, and it is relatively simple to correct. In most cases, we can simply ask the user to provide another file. Here’s an example of some code that may cause this exception:

String filename = "";
Scanner scanner = new Scanner(new File(filename)); // throws FileNotFoundException

In this case, we are providing a blank filename, which causes the program to throw a FileNotFoundException.

IOException

Java 8 API Reference

An IOException is thrown whenever the program encounters a problem performing some input or output operation. This can be due to an error in the file it is reading from, a bug in the underlying operating system, or many other problems that can occur while a program is reading or writing data.

For example, reading input using a Scanner object can result in an IOException, both when reading from the terminal using System.in, or when reading from a file. So, in general, anytime we are reading input from any source, we must always consider the fact that an IOException can occur.

Thankfully, these errors are very rare on most modern computers, but they are something that can be dealt with.

NumberFormatException

Java 8 API Reference

A NumberFormatException is used in Java when we try to convert a string to a number but it doesn’t work. See the sample code below to see how this could occur:

String s = "abc";
int x = Integer.parseInt(s); // throws NumberFormatException
double d = Double.parseDouble(s); // throws NumberFormatException

This exception is really useful when we are asking the user to provide a number that fits a particular format. By detecting this exception, we know that the user entered an invalid number, so we may have to prompt the user for another input.

NoSuchElementException

Java 8 API Reference

A NoSuchElementException happens when a Scanner object tries to read input when there is no input available to read.

Scanner reader = new Scanner(System.in);
int x = reader.nextInt(); // throws NoSuchElementException if the input is empty;

IllegalArgumentException

Java 8 API Reference

This exception is typically used by developers to indicate that a particular input to a program is invalid. Many developers also use it in their own code as a custom exception.

AssertionError

Java 8 API Reference
Programming with Assertions

An AssertionError happens when our code reaches an assertion that fails. An assertion is a check added by the programmer to verify that a particular situation doesn’t occur in the code, such as a variable being negative or an array being too large. Java supports the use of a special keyword assert that can be used to add these assertions to our code. By default, the Java runtime environment ignores any assert keywords, but they can easily be enabled by adding -ea as an argument to the java command when running a program. This is a helpful step when debugging a new application. We’ll learn more about assertions and how they can be used effectively in a later chapter.

For now, here’s a quick example of an assertion that would produce an AssertionError:

int x = 5;
int y = 3;
assert x + y < 7; // throws AssertionError if enabled

Errors

Beyond exceptions, there are a few unrecoverable errors that may occur in our Java code. Recall that errors are special types of exceptions that should not be handled by our programs, and should instead be allowed to cause our programs to crash. Some examples are:

  • StackOverflowError - occurs when we have used up all of the available stack space in the Java virtual machine .. this usually happens with because of an infinite loops
  • OutofMemoryError - occurs when we have used up all of the available system memory … this rarely happens but again can be caused by an infinite loop

In addition, the Java compiler helps us detect many other errors, such as syntax and type errors that would cause our code to be unusable. Other languages, such as Python, have to deal with these errors at runtime, so there are many more unrecoverable errors in that language compared to Java.

References:

Checked vs. Unchecked Exceptions

Beyond the difference between exceptions and errors we’ve already discussed, Java also makes a distinction between two types of exceptions, checked exceptions and unchecked exceptions. Let’s look more closely at each of these groups below.

Checked Exceptions

A checked exception is detected by the Java compiler. When it detects a checked exception, the compiler then looks to see if the exception will be detected and handled within the code. If not, it will then check to see if the method containing that code includes a throws clause1 at the end of the method declaration that includes this particular exception. If neither of those is the case, then the compiler will output an error and refuse to compile the code. So, the developer must add one of these two things to the code in order for the compiler to be satisfied.

Unchecked Exceptions at Compile Time
error: unreported exception someException; must be caught or declared to be thrown

If you get this type of message from the compiler, you have 2 choices.

  1. Add throws someException to the signature line
    public void someMethod() throws someException {}
  2. Handle the exception with a try-catch block (discussed in this module)

In this class you will generally handle the exception.

Unchecked Exceptions

An unchecked exception is one that is not detected by the Java compiler. These are exceptions that can occur at runtime, but would be difficult or impossible to detect when compiling the code. However, in many cases they can easily be detected or prevented from within the program itself. So, the compiler does not require us to add code to either handle those exceptions or declare them to be thrown in to our method declaration.

An ArithmeticException is a good example of an unchecked exception. Any division operation could result in an ArithmeticException, but the compiler does not require us to add additional code to handle that exception or declare it to be thrown. However, it is still good practice to either detect and handle this error, or use code or assertions to prevent it from occurring in the first place.

Errors

As discussed before, errors are a special type of exception that cannot be easily handled by the program. These are usually due to issues in the underlying operating system, and typically cause the program to crash.

References


  1. throws is a keyword in Java. It is only used to tell the compiler to ignore the lack of a handler for the checked exception ↩︎

Try-Catch

YouTube Video

Video Materials

Programs written in Java can have many exceptions occur on a regular basis. Thankfully, as we learned earlier, it is possible to detect and handle these exceptions in our code directly, in order to prevent our program crashing and causing the user more stress. Let’s see how we can perform this step in Java. A Try-Catch construct is synonymous with “handler”.

Try-Catch

In Java, we use a Try-Catch construct to detect and handle exceptions in our code. Let’s look at a quick example, and then we can discuss how it works.

import java.util.Scanner;
import java.lang.Exception;

public class Example{

  public static void main(String[] args){
  
    Scanner reader;
    
    try{
    
      reader = new Scanner(System.in);
      int x = Integer.parseInt(reader.nextLine());
      System.out.println(x);
      
    }catch(Exception e){
      System.out.println("Error!");
    }
    
  }
}

First, we use the try keyword, followed by curly braces {} around a block of code. If any exceptions occur in the code contained in the try statement, we can add catch statements to handle that exception.

Directly following the closing curly brace after the try block, we must include at least one catch statement. The catch keyword is followed by the name of an exception and a variable for that exception in parentheses (). In this example, we are just catching the generic Exception type, which will match any catchable exception. We’ll see how we can detect specific exceptions in a later example. Then, we have one more block of code contained in curly braces {} that is executed when we detect an exception. We can use the variable for the exception, in this case e, to access additional details about the exception we’ve detected.

Finally, notice that we also must add import java.lang.Exception to the top of our file. We’ll need to import any exceptions we want to catch in order to use them.

Which Exceptions to Catch?

When writing code, it can sometimes be very difficult to even know which exceptions to expect from a particular piece of code. Thankfully, the Java 8 API Reference includes quite a bit of information, including which exceptions can be thrown by a particular method.

Let’s return to our earlier example. Here is the code contained in the try block:

reader = new Scanner(System.in);
int x = Integer.parseInt(reader.nextLine());
System.out.println(x);

While this may look like a very simple few lines of code, there are actually several exceptions that could be produced here. Below is a list of them, followed by a link to the Java 8 API reference for the source of that exception:

When writing truly bulletproof code, it is a good idea to attempt to catch and handle all of these exceptions if possible. You can always refer to the official Java 8 API reference to see what exceptions could be produced by any methods you use that are a part of the main Java language. Later on in this chapter we’ll discuss some best practices when it comes to detecting and handling exceptions in code.

Handling Multiple Exceptions

Of course, we can add multiple catch statements after any try block to catch different types of exceptions.

import java.util.Scanner;
import java.io.IOException;
import java.lang.NumberFormatException;

public class Example{

  public static void main(String[] args){
  
    Scanner reader;
    
    try{
    
      reader = new Scanner(System.in);
      int x = Integer.parseInt(reader.nextLine());
      System.out.println(x);
      
    }catch(IOException e){
      System.out.println("Error: IO Exception!");
    }catch(NumberFormatException e){
      System.out.println("Error: Input Does Not Match Expected Format!");
    }
    
  }
}

In the example code above, we see three different catch statements, each of which will handle a different type of exception. When an exception occurs in the code contained in a try block, the Java runtime will create the exception, and then it will search for the first handler that matches. So, it will begin with the first catch statement, and see if the exception created matches the type of the exception in parenthesis. If so, it will execute the code inside of that catch block. If not, it will continue to the next catch statement. If none of the catch statements match the exception, then it will be thrown and cause the program to stop executing.

Exception Hierarchy

The exceptions in Java form a hierarchical structure, meaning that an exception may match multiple types. For example, all exceptions that programs can catch are based on the generic Exception type. So, FileNotFoundException and ArrayIndexOutOfBoundsException would both match the Exception type.

In addition, many exceptions are descended from the IOException type. For example, FileNotFoundException is based on IOException, which itself is based on the Exception type. So, a FileNotFoundException would match any of those three types of exceptions. It can make things a bit tricky!

So, how can we know what types of exceptions we are dealing with, and what the hierarchy is? Thankfully, the Java 8 API Reference contains information about all possible exceptions, including the entire hierarchy of that exception.

For example, here is a screenshot from the FileNotFoundException page showing its hierarchy:

FileNotFoundException Hierarchy FileNotFoundException Hierarchy

Because of this, we must be careful about how we order the catch statements. In general, we want to place the more specific exceptions first (the ones further down the hierarchy), and the more generic exceptions later.

Let’s look at an example of poor ordering of exception handlers:

import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.NumberFormatException;

public class Example{

  public static void main(String[] args){
  
    Scanner reader;
    
    try{
    
      reader = new Scanner(new File("input.txt"));
      int x = Integer.parseInt(reader.nextLine());
      System.out.println(x);
      
    }catch(IOException e){
      System.out.println("Error: IO Exception!");
    }catch(FileNotFoundException e){s
      System.out.println("Error: File Not Found!");
    }catch(NumberFormatException e){
      System.out.println("Error: Input Does Not Match Expected Format!");
    }
    
  }
}

In the code above, we are catching the IOException first. So, if the code produces a FileNotFoundException, it will be caught and handled by the first catch statement, since FileNotFoundException is also an IOException. Therefore, we wouldn’t want to order our catch statements in this way.

Reading Files

This chapter introduces the basic code for reading files using a Scanner object. It is mainly presented as a way to introduce some of the more common exceptions related to file input and output. We’ll cover the actual process of reading and writing from files in a later chapter.

Try It!

Let’s see if we can write a very simple program to catch and handle a few common exceptions. Here’s some code to start with:

import java.util.Scanner;
import java.io.File;

public class Try{

  public static void main(String[] args){
  
    Scanner reader;

    reader = new Scanner(System.in);
    int x = Integer.parseInt(reader.nextLine());
    int y = Integer.parseInt(reader.nextLine());
    int z = x / y;
    
    System.out.println(z);
    
  }
}

For this example, place this starter code in Try.java, open to the left, and modify it to catch and handle the following exceptions by printing the given error messages:

  • NumberFormatException - print “Error: Input Does Not Match Expected Format!”
  • NoSuchElementException - print “Error: Too Few Inputs Provided!”
  • ArithmeticException - print “Error: Divide by Zero!”

Any other exceptions can be ignored. Don’t forget to add import statements at the top of the file for each type of exception we need to catch!

Subsections of Try-Catch

Throw and Throws

YouTube Video

Video Materials

Beyond catching exceptions, there are a few other important concepts to learn related to exceptions. Let’s go over a couple of them.

Throws

When the Java compiler finds a checked exception, we must either surround that code in a Try-Catch statement, or we must declare the exception to be thrown by adding throws to our method declaration, followed by the exception type.

Here’s an example:

import java.util.Scanner;
import java.io.File;

public class Throw{
  
  public static void main(String[] args){
    
    Scanner reader;
    
    reader = new Scanner(new File("input.txt"));
    int x = Integer.parseInt(reader.nextLine());
    System.out.println(x);
    
  }
}

The code above handles user inputs. However, it does not includes the throws keyword. So, when we try to compile this code, we’ll get the following error message.

8j-except/Throw.java:11: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
      reader = new Scanner(new File(args[0]));
               ^
1 error

To allow this code to compile, we must simply add throws FileNotFoundException to the end of our method declaration to tell the compiler that we aren’t going to handle that exception in our code. We’ll also have to add import java.io.FileNotFoundException to the top of the file. Notice that we aren’t using throws Exception this time, since we now know more about how exceptions work. As we’ll discuss later in this chapter, it is always better to include a specific exception instead of a generic one whenever possible.

Of course, we may also want to use a Try-Catch statement to handle this error directly in our code. We’ll discuss those tradeoffs later as well.

Throw

We can also generate our own exceptions using the throw keyword. This allows us to create new exceptions whenever needed. Let’s look at an example:

import java.util.Scanner;
import java.io.File;
import java.lang.ArithmeticException;
import java.io.FileNotFoundException;

public class Throw{
  
  public static void main(String[] args) throws FileNotFoundException{
    
    Scanner reader;
    
    reader = new Scanner(new File("input.txt"));
    
    double x = Double.parseDouble(reader.nextLine());
    double y = Double.parseDouble(reader.nextLine());
    
    if(y == 0.0){
      throw new ArithmeticException("Divide by Zero!");
    }else{
      System.out.println(x / y);
    }
    
  }
}

In this code, we are asking the user to input two floating point numbers. Then, we will output the first number divided by the second. However, we want to avoid the situation where the second number, the divisor, is $0$. Since Java doesn’t generate an ArithmeticException when dividing by $0$ using floating point numbers, we can do that ourselves using an If-Then statement and the throw keyword.

Following the throw keyword, we must create a new Exception. In this case, we are creating a new ArithmeticException, but any of the exception types we’ve learned about so far will work the same way. Inside of the parentheses, we can provide a helpful error message to go along with this exception.

Try It!

Let’s see if we can use these keywords in another example. We’ll start with this code:

import java.util.Scanner;
import java.io.File;

public class Throw{
  
  public static void main(String[] args){
    
    Scanner reader;
    
    reader = new Scanner(new File(args[0]));
   
    // *** ADD CODE HERE ***
    
  }
}

Place this code in Throw.java and modify it to do the following:

  1. If the file given in args[0] does not exist, it should throw a FileNotFoundException. We’ll need to add the throws keyword and additional information in the correct place.
  2. It should read a single string of input from the user. To make this simple, don’t forget to use the trim() method to remove any extra whitespace!
  3. If the string does not begin with a capital letter, it should throw a new IllegalArgumentException that contains the error message “Proper Capitalization Required!”
  4. Of course, if the string is empty, it should throw a StringIndexOutOfBoundsException when it tries to access the first character of the string. (Hint: Will the String.charAt() method throw this exception for us? We can check the Java Documentation to find out)
  5. Otherwise, it should print the string to the terminal.
Command-Line Arguments

In the example above, we are using the code args[0] to access the first command-line argument provided to the program. As you can tell by the data type in the parameter of the main method, args is an array of strings.

When running a Java program from the terminal, we can provide command-line arguments after the name of the file, as in this example:

java Throw input.txt

In this example, the string "input.txt" will be stored in args[0] as the first command-line argument, and it will be accessible in the program. This is a great way to run a program by providing an input file to read from.

More command-line arguments can be included, separated by spaces:

java Throw input.txt output.txt 

In this example, "input.txt" would be stored in args[0] and "output.txt" would be stored in args[1].

Subsections of Throw and Throws

Finally

YouTube Video

Video Materials

When dealing with exceptions in our code, sometimes we have an operation that must be executed, even if the earlier code throws an exception. In that case, we can use the finally keyword to make sure that the correct code is executed.

Finally

To understand how the finally keyword works, let’s take a look at an example:

import java.util.Scanner;
import java.lang.NumberFormatException;
import java.lang.IllegalArgumentException;

public class Finally{
  
  public static void main(String[] args){
    
    Scanner reader;
    reader = new Scanner(System.in);
    try{
      int x = Integer.parseInt(reader.nextLine());
      if(x < 0){
        throw new IllegalArgumentException("Input must be greater than 0!");
      }
    }catch(NumberFormatException e){
      System.out.println("Error: Must input an integer!");
    }catch(IllegalArgumentException e){
      System.out.println(e.getMessage());
    }finally{
      System.out.println("Finally Block");
    }
    System.out.println("After Try");
  }
}

This program will read an integer from the terminal. If the integer is greater than or equal to 0, it will do nothing except print the “Finally Block” and “After Try” messages. However, if the input is less than 0, it will throw an IllegalArgumentException. Finally, if the input is not a number, then an InputMismatchException will be thrown and handled. In each of those cases, it will also print the “Finally Block” message.

Let’s run this program a few times and see how it works. First, we’ll provide the input “5”:

$ java Finally
5
Finally Block
After Try

Here, we can see that the code in the finally block always runs when the program is finished executing the statements in the try block.

Let’s run it again, this time with “-5” as the input:

$ java Finally
-5
Input must be greater than 0!
Finally Block
After Try

Here, we can see that it prints the error message caused by the IllegalArgumentException, then proceeds to the finally block. So, even if an exception is thrown while inside of the try block, the code in the finally block is always executed once the try block and any exception handlers are finished.

Here’s one more example, this time with “abc” as the input:

$ java Finally
abc
Error: Must input an integer!
Finally Block
After Try

Once again, we see that the code first handles the NumberFormatException, and then it will execute the code in the finally block.

In a later chapter, we’ll learn more about how to use the finally block to perform important tasks such as closing open files and making sure we don’t leave the system in an unstable state when we handle an exception.

Subsections of Finally

Try with Resources

YouTube Video

Video Materials

Lastly, Java includes a special type of Try-Catch statement, known as a Try with Resources statement, that can perform some of the work typically handled by the finally block.

Try with Resources

Let’s look at an example of a Try with Resources statement:

import java.util.Scanner;
import java.io.File;
import java.lang.NumberFormatException;
import java.io.FileNotFoundException;

public class Resources{
  
  public static void main(String[] args){
    
    try(
      Scanner reader = new Scanner(new File("input.txt"))
    ){
      
      int x = Integer.parseInt(reader.nextLine().trim();
      System.out.println(x + 5);
      
    }catch(NumberFormatException e){
      System.out.println("Error: Invalid Number Format!");
    }catch(FileNotFoundException e){
      System.out.println("Error: File Not Found!");
    }
    
  }
}

In this example, there is a new statement after the try keyword, surrounded by parentheses. Inside of that statement, we are declaring and initializing a new Scanner object to read data from a file. That Scanner object is the resource that we are using in our Try with Resources statement. We can add multiple resources to that section, separated by semicolons ;.

When the code in the try statement throws an exception, Java will automatically try to close the resources declared in the Try with Resources statement. So, when we are reading input from a file, it would close that file and make sure that it isn’t damaged or left open when our program crashes.

In addition, any additional exceptions thrown when trying to close the file are suppressed by the system, so we only see the exception that caused the initial error. This is much better than using a finally statement to close the file, since we’d have to run the risk of throwing a second exception inside of the finally statement.

Any Java class that implements the AutoCloseable interface can be used as a resource in this way. The Java 8 API documentation for AutoCloseable lists all known classes that implement that interface. We’ll discuss interfaces more later in this course.

Specifically, this type of statement is great when writing programs that will handle large amounts of input, either by connecting to a database, reading from a file, or using the Internet to communicate with a server. A Try with Resources statement is a great way to make sure those programs are able to handle exceptions without leaving the system in an unstable state.

Never Use with System Resources

Try-with resources should never be used in concert with Scanners Readers or Buffers connected to System resources. For example

// attach scanner to System stdin
Scanner reader = new Scanner( System.in);  
// use as part of try with resources
try (Scanner scan = reader){...

} catch (Exception e){ 

}
// now System.in is closed and unavailable to rest of program

is a bad construct.

Subsections of Try with Resources

Best Practices

Now that we’ve seen lots of information about how to throw, catch, and handle exceptions in Java, it is a great time to discuss some of the best practices we can apply in our code to make it as readable and bulletproof as possible. While these aren’t strict rules that must be followed all the time, they are great things to keep in mind as we write code that deals with exceptions.

Leave No Exception Unhandled

As much as possible, we should write our code to handle any exception we can reasonably expect our users to run into when using our programs. We cannot assume that users will always provide the correct input in the correct format, and a single typo by a user should not cause our entire program to crash.

Instead, we should always use Try-Catch statements whenever possible to detect and handle those errors as simply as possible. In addition, if the program is interactive, we can combine that approach with the use of loops to prompt the user to provide new input to resolve the error.

The Java 8 API is a great resource to determine which exceptions could be thrown by any methods used in our code.

Don’t Substitute Unchecked Exception Handling for Value Checking

You can go “try catch” crazy. Exception handling is powerful, but also really, really slow.

For example, this statement using exceptions is very slow:

try{
    ratio = x / y;
}catch ArithmeticException e{
    System.out.println("Cannot divide by zero");
}

but this version of the statement using a simple conditional statement:

if (y !=0){
    ratio = x / y;
}else{
    System.out.println("Cannot divide by zero");
}

executes a lot more efficiently. In this example the if statement is about 60 times faster than the try-catch statement.

In fact, most unchecked exceptions can be avoided through value checking. In this course, we may direct you to throw and catch exceptions of this type for practice in exception coding.

Don’t use Try-Catch blocks for Control Flow

It is BAD practice to have implied control flow embedded and disguised as try catch. For example, if we are to accept input from either a file given as a command line argument or the keyboard. This first code block is acceptable:

Scanner reader;
if args.length() == 1{
    try{
     reader = new Scanner(new File(args[0])); 
    }
    catch Exception e{
      System.out.println("error accessing file");
      return;
    }
  } else {
    reader = new Scanner(System.in);
  }

This next code block is bad style:

   try{
      scanner = new Scanner(new File(args[0]));
    }catch(FileNotFoundException e){
      System.out.println("FileNotFoundException: " + e.getMessage());
      scanner = new Scanner(System.in);
    }
  }

Most readers of your code will not expect “flow logic” to be in the catch blocks and may miss it.

Be Specific

We should also always strive to use specific exceptions in our catch statements whenever we can. So, if we are opening a file, we should include a catch statement for a FileNotFoundException, and not just a single generic Exception. Even though this means we may have to include several catch statements, it is much better in the long run because it allows us to know exactly what happened when our code has a problem.

In addition, we should always make sure we catch the most specific exceptions first, before any more generic exceptions. As we saw earlier, if we try to catch an IOException before a FileNotFoundException, we won’t be able to tell when we’ve reached a FileNotFoundException at all, since it is a subtype of the IOException.

Use Messages and Stack Traces

The Java Exception class includes some very helpful methods we can use to get additional information when we encounter an exception.

Here’s an example to show what we can learn:

import java.lang.ArithmeticException;

public class StackTrace{
  public static void main(String[] args){
    try{
      int x = 5 / 0;
    }catch(ArithmeticException e){
      System.out.println("Error: " + e.getMessage());
      e.printStackTrace();
    }
  }
}

When this code is executed, it will produce the following output:

$ java StackTrace
Error: / by zero
java.lang.ArithmeticException: / by zero
        at StackTrace.main(Finally.java:6)

The first line will use e.getMessage() to get the short version of the error, in this case the text “/ by zero”. The second line uses e.printStackTrace() to print a full stack trace showing the location of the error in code.

In general, it is best to only show the short error message to users, but in some cases it may be better to add our own message. The text “/ by zero” isn’t very clear, even to developers. Instead, we could say “Error: divisor cannot be zero” to make the error message clearer.

The information contained in the stack trace is very helpful to developers when trying to debug and fix problems in the code, but that information is very confusing to users. As we learn how to build more advanced programs, we’ll see how to record and log those error messages and stack traces for debugging, but hide them from our users.

To see what other methods are available, refer to the Throwable entry in the Java 8 API reference.

Don’t Ignore Exceptions

Another big problem is that many developers tend to catch exceptions, only to ignore them so their program doesn’t crash. Here’s a quick example:

int x = 0;
Scanner reader = new Scanner(System.in);
try{
  x = Integer.parseInt(reader.nextLine());
}catch(Exception e){
  //do nothing
}

In this code, if the user inputs something that can’t be converted to an integer, the code just silently ignores the exception and proceeds with x still storing the value $0$. While that may not cause issues, it can also make it very frustrating to debug later issues or changes in this program. So, it is always best to output an error message when an exception occurs, even if it can be easily ignored without additional input from the user. This will make it easier to debug additional issues later on in development.

Bulletproof Code

Here’s a simple program that asks the user for input, and will repeat the question until a valid input is received. This code is designed to handle many common situations and exceptions:

import java.util.Scanner;
import java.lang.NumberFormatException;
import java.util.NoSuchElementException;

public class HandleInput{
  public static void main(String[] args){
    int x = 0;
    try(
      Scanner reader = new Scanner(System.in)
    ){
      while(x <= 0){
        try{
          System.out.print("Please input a positive integer: ");
          x = Integer.parseInt(reader.nextLine());
          if(x <= 0){
            System.out.println("Error: Negative Integer Detected!");
          }
        }catch(NumberFormatException e){
          System.out.println("Error: Integer Not Found!");
          reader.nextLine(); // bypass bad input and try again
        }catch(NoSuchElementException e){
          System.out.println("Error: No Input Found!");
        }catch(Exception e){
          System.out.println("Error: Unknown Input Error!");
          return; //probably can't recover, so stop executing
        }
      }

      System.out.println("You entered " + x);
      
    }catch(Exception e){
      System.out.println("Error: Unable to Open Scanner!");
    }
  }
}

There are several items in this code that help make it very bulletproof:

  1. First, when the program opens a Scanner object, it uses a Try With Resources statement to make sure that it will be properly closed when we are done with it. As we’ve discussed, this is a good practice when handling input via files, but really any input object can be handled in this way.
  2. The Try With Resources statement has a single catch block to handle any exceptions that arise from opening the Scanner. This prevents those exceptions from being thrown to the user. If we are opening a file, we’ll also need to handle the FileNotFoundException here.
  3. Inside of the While loop, there is a second Try-Catch statement to handle errors that come from reading an individual input, such as an NumberFormatException or a NoSuchElementException.
  4. If an NumberFormatException is caught, we’ll need to use next() to successfully read the next token from input before we continue, or else it will try to read the same thing again.
  5. In addition, that inner Try-Catch statement will also catch any generic Exceptions that might occur when reading input. In that case, we use the return keyword to stop executing the program, since it may be difficult to continue to read input in that case. Thankfully, since we are using a Try With Resources block, the Scanner object will be correctly closed for us automatically. In addition, any finally blocks would be executed before the program stops.
Don’t Exit Early!

We should not use System.exit() to exit our program, which you may find on many online resources. The System.exit() method directly ends the Java Virtual Machine, or JVM, without properly closing any resources or executing finally blocks. It can also make your code much more difficult to maintain and test. This can be very dangerous!

Of course, there are many things that can be done differently in this code, depending on our preferences and how we’d like our program to function. This is just one possible way to build useful code that handles several exceptions.

A Worked Example

Let’s work through an entire example program together to see how we can build programs that handle exceptions quickly and easily, without allowing the program to crash when invalid input is received.

Problem Statement

Consider the following problem statement for a driver-class program Example.java:

Write a program to accept a list of numbers, one per line from the terminal.

The numbers can either be positive whole numbers or positive floating point numbers. The program should continue to accept input until a negative number is received. Zero is an acceptable input.

Once a negative number is received, the program should print the largest and smallest number provided as input (not including the the negative number end input). Sample outputs are shown below.

The program should catch and handle all common exceptions. When an exception is caught, the program should print the name of the exception. It should then continue to accept input (if possible) or exit the program (if not possible).

Sample Inputs & Outputs

Here’s an example of an expected input for the program:

5.0
3
8.5
7
k
2.3.6
-1

Here is the correct output for that input:

NumberFormatException
NumberFormatException
Maximum: 8.5
Minimum: 3.0

Pause a moment to think about control flow, loops and exceptions you might need to handle.

When first starting out in coding and exception handling, it is often best to first write the code without exception handling, test it, then add the exception stuff. Exception handlers can hide logic errors.

The code without exception handling

We’ll start out with the code to “attach” a Scanner to the correct input source.

import java.util.Scanner;
import java.lang.NumberFormatException;
import java.util.NoSuchElementException;


public class Example{
    public static void main (String[] args) {
        Scanner scanner = new Scanner(System.in);

    }
}

Next let’s use a do-while loop to handle input until there is a negative number. We will treat all inputs as doubles.

double input;
do{
    input = Double.parseDouble(scanner.nextLine().trim());

}while(input >= 0.0);

If the input is negative, we do nothing. Otherwise we keep track of the minimum and maximum number seen. Note, that the first number input is both the min and max number seen. We’ll handle this by observing that an acceptable entered number cannot be less than zero so initial values for the min and max can bet set to -1.0 as a sentinel value.

double max = -1.0;
double min = -1.0;
double input = 0.0;

do{
    input = Double.parseDouble(scanner.nextLine().trim());
      if (input >= 0.0 ){
        if (min > input || min < 0.0){
          min = input;
        }
        if (max < input || max < 0.0){
          max = input;
        }
      }
} while (input >= 0.0);

Lastly we would print the min and max out.

double max = -1.0;
double min = -1.0;
double input = 0.0;

do{
    input = Double.parseDouble(scanner.nextLine().trim());
      if (input >= 0.0 ){
        if (min > input || min < 0.0){
          min = input;
        }
        if (max < input || max < 0.0){
          max = input;
        }
      }
} while (input >= 0.0);

System.out.println("Maximum: " + max);
System.out.println("Minimum: " + min);

We leave it for you to finish the “without exception handling” program and test it from the terminal.

Adding Exception handling

Although rare, a Scanner object connected to System.in can throw a NoSuchElementException when nextLine() is called. This can occur when:

  • the program has inadvertently closed System.in (more on this is discussed in the chapter on Files)
  • a redirected stream lacks the proper number and/or types of inputs (see below)
Directing a File to Stdin

In Linux, the < operator can be used at the terminal to send the contents of a file in place of stdin.

java Example < tests/example1.txt

You can see this “trick” if you have Example.java working. The command java Example < tests/example1.txt will provide the input from the file example1.txt that is stored in the tests folder to the program, just as if it was typed directly in the terminal by hand.

Let’s catch the NoSuchElementException,

double max = -1.0;
double min = -1.0;
double input = 0.0;

do{
  try{
      input = Double.parseDouble(scanner.nextLine().trim()); 
  }
  catch (NoSuchElementException e){
      System.out.println("NoSuchElementException");
      return;  //exits the program
  }

  if (input >= 0.0 ){
    if (min > input || min < 0.0){
      min = input;
    }
    if (max < input || max < 0.0){
      max = input;
    }
  }

} while (input >= 0.0);

System.out.println("Maximum: " + max);
System.out.println("Minimum: " + min);

Here we catch the Exception and exit the program. Remember to catch a specific Exception you must import it.

The other thing that can go awry is the parsing. Reading a String that would not parse into a Double, which will throw a NumberFormatException. However, from the example inputs it is clear that we should not exit on this condition but instead keep accepting input. continue will reset us to the next loop.

double max = -1.0;
double min = -1.0;
double input = 0.0;

do{
  try{
      input = Double.parseDouble(scanner.nextLine().trim()); 
  }
  catch (NoSuchElementException e){
      System.out.println("NoSuchElementException");
      return;  //exits the program
  }
  catch(NumberFormatException e ){
      System.out.println("NumberFormatException");
      continue;
  }

  if (input >= 0.0 ){
    if (min > input || min < 0.0){
      min = input;
    }
    if (max < input || max < 0.0){
      max = input;
    }
  }

} while (input >= 0.0);

System.out.println("Maximum: " + max);
System.out.println("Minimum: " + min);

Testing

Finally, we may want to do some quick testing of our program. In theory, it should handle any inputs provided. When testing, we should always see if we can find some input that breaks our program, then adjust the program as needed to prevent that problem. Of course, we should always make sure that it produces the correct answers, too.

Subsections of A Worked Example