Lesson Thursday

Understanding Exceptions

The next thing we'll introduce to make our app more full featured are Exceptions.

You have already been working with Exceptions for some time - remember that in our first week of Java, we were communicating with our user by wrapping BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); in a try/catch block.

We've also seen Exceptions that our code produces, such as NullPointerException and Sql2oException in our code. Let's learn a little bit more about Exceptions, what they are, and how we can create custom ones to use in our code.

Overview of Exceptions

Exceptions in programming are language agnostic in concept, meaning we have Exceptions in many different languages and Frameworks. Exceptions are, simply put, problems or errors that arise when the code is built or executed.

Compiler errors that prevent the code from being built (missing variables, syntax errors) are called Checked Exceptions, as they are checked when the code is built. Checked exceptions always need to be dealt with before our code runs.

While Java already has a lot of error-checking for runtime errors built in, it can still happen that a RuntimeException (i.e a crash while the app is running) will occur when the program built fine, but has problems when executing: A classic example of this is that we get a NPE (NullPointerException) when a user submits an empty form to our app. A Runtime error is an Unchecked Exception - it doesn't always occur, and it is not due to a code error per se. Runtime Errors should always be handled, meaning that we should write code to ensure that the application knows how to act when it hits a bump. Examples of runtime exceptions are NPE, ArrayIndexOutOfBoundsException, IOException, and more.

Handling

As mentioned above, Exceptions should always be addressed as they affect user experience very negatively. If, for example, you know that some code you write sometimes causes an ArrayIndexOutOfBoundsException, you should re-write your code to prevent this from happening.

Sometimes, however, it isn't possible or practical for us to rewrite our code to prevent errors - for example, if we are relying on our user to provide us some input, we can account for some possibilities of non-standard input, but not all. And we can make sure our SQL Strings are always correct, but we may not be able to guarantee that we won't lose connection to the database sometimes. This is why special ways exist that allow us to handle any issues gracefully. This does not mean getting by with bad code, it means telling our app how to deal with unpredictability.

Try / Catch

As mentioned above, try/catch blocks are one tool we have to handle errors.

Here is the basic anatomy of a try / catch:

public class Test {
   public static void main(String[] args) {
       int indexToPull;
       String[] pdxTeams =  {"TrailBlazers","Ducks", "Lumberjax", "Timbers", "Beavers", "Thorns"};
       try {
           // Try block to handle code that may cause exception
           indexToPull = 23;
           String team = pdxTeams[indexToPull];
           System.out.println("Try block message");
       } catch (ArrayIndexOutOfBoundsException e) {
           // This block is to catch ArrayIndexOutOfBoundsException
           System.out.println("Error:That team doesn't exist!");
           e.printStackTrace(); //print out some context to the error.
       }
       System.out.println("I'm out of try-catch block in Java.");
   }
}

Note, that ArrayIndexOutOfBoundsException is actually creating an object of the ArrayIndexOutOfBoundsException class and assigning it to the variable e. It has some methods available to it like getMessage(), getStackTrace(), and printStackTrace() in case you wanted to show a user of your applications why they received an error.

It consists of two main parts: the try block and the catch block. The try block contains the code we know might cause an error, the catch block contains the kind of error we expect the code might produce - see the Cheat Sheet for a list of the most common standard exceptions. Add on libraries such as Sql2o will also have their own exceptions that are not part of Java core.

A variation of try/catch is try with resources , which is very similar to a standard try/catch, but offers some extra material to work with. See this example:

try (Scanner scanner = new Scanner(new File("testRead.txt"));//the () part contains the resources we can try with.
    PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
    while (scanner.hasNext()) {
    writer.print(scanner.nextLine());
    }
}

Here, the try block has "resources" i.e some extra stuff to work with, similarly to the condition to test again in an if cause. These aren't too common, as they were introduced by Java 7, but you may see them around. They function the same way as a regular try/catch.

In the next lesson, we'll learn how to create custom exceptions and use them in our Java applications.