Exception Handling in Java

What are Java Exceptions ?

Java Exceptions are events that occur during the execution of Java programs that disrupt the normal flow of the execution (e.g., divide by zero, array access out of bound, null pointer exceptions, etc.).

It is considered a good practice to use exceptions in Java so that we can separate error-handling code from regular code.

You can think about Exceptions as runtime errors that might or might not manifest at runtime, depending on the inputs that the program receives.

Exceptions are different from compilation errors that arise at compilation time (before running the program) due to an incorrect Java syntax.

A very common runtime exception is the infamous NullPointerException.

For example, consider this method:

	static int sumLengths(String s1, String s2) {
		return s1.length() + s2.length();
	}

What happens if the method is invoked passing a null reference as a parameter? For example:

	String s2 = null;
    System.out.println(sumLengths("hello", s2));

An exception is thrown and the program crashes:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "s2" is null
	at ExampleNPE.sumLengths(ExampleNPE.java:10)
	at ExampleNPE.main(ExampleNPE.java:6)

This is because Java cannot invoke s2.length() if s2 is null.

An Interesting fact about NullPointerException!

Speaking during a software engineering conference in 2009, Tony Hoare the creator of the null reference publicly apologized for inventing it, as NullPointerException caused too many software crashes.

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.” Tony Hoare.



In Java, an exception is an object! (surprised? I doubt it!… you should know by now that in Java everything is an object 😃 ) that wraps an error event that occurred at runtime.

For example, let’s consider this simple program:

If you run it, you will get a runtime exception:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
    at Main.main(Main.java:7)

This is because the statement System.out.println(array[3]); is trying to access the 4th element of an array that has only three elements (remember that array indexes start from 0, so array[3] is accessing the fourth element).

This interrupts the program execution at the line of code where the exception was thrown (line 7 of Main.java). In fact, the program does not print System.out.println("program ended");.

A Java exception usually contains:

  • Information about the error including its type (e.g., ArrayIndexOutOfBoundsException ), and the class, method and line number that triggered the exception (e.g., at Main.main(Main.java:7)),

  • The state of the program variables when the error occurred (e.g., Index 3 out of bounds for length 3 ).

In this lecture, you will learn the different types of Exceptions, you will see that Exception objects can be thrown and caught. You will also learn how to create, throw and catch user-defined exceptions specific to your program.






How to handle exceptions?

In general, good programmers write code to handle exceptions – to avoid interrupting the execution of the program.

Exceptions are handled by catching them!

This is accomplished with the try and catch statements. Remember that you cannot have a try without a catch, and you cannot have a catch without a try.

If we want to catch the exceptions in the previous program, we can do it like this:

int[] array = new int[3];
array[0] = 10;
array[1] = 20;
array[2] = 30;

try {
    System.out.println(array[3]);
    System.out.println("this will not be printed because an exception is thrown");
} catch(ArrayIndexOutOfBoundsException e){
    System.out.println("You tried to access an element of the array that does not exist");
}

System.out.println("program finished");

When the ArrayIndexOutOfBoundsException exception occurs within the try section, the program will execute the code inside the catch section. In this case, we are merely printing an error message. Generally, depending on what we’re doing, we might try to do some actions to recover from the exception or log some important information.

Now the program will print:

You tried to access an element of the array that does not exist
program finished

There are few important things to notice:

  1. Now the program reaches the end! This is because by catching the exception, the program is able to handle the exception and therefore does not crash.

  2. Because ArrayIndexOutOfBoundsException belongs to the package java.lang we don’t need to import the class (e.g., import java.lang.ArrayIndexOutOfBoundsException). All the classes in the package java.lang are imported by default (e.g., Object, String, Integer).

  3. The catch will only catch the exception of type ArrayIndexOutOfBoundsException. If other exception types are thrown, these will not be caught by the catch statement.

  4. ArrayIndexOutOfBoundsException e is declaring object reference variable e of type ArrayIndexOutOfBoundsException, this allows us to refer to the exception that has occurred! We can get whatever information we want out of the exception, for example the error message (e.g., e.getMessage()).

Optionally, you can also have a finally section after the catch, where you can put the code that you want that is always executed no matter if the exception is thrown or not:

try {
    // Try to do something in this section...
} catch(Exception e) {
    // Some exception was thrown in the section above, handle the exception here.
} finally {
    // Regardless of whether an exception was thrown, execute this section.
}

An example why we would want to use the finally section, is if we are trying to read a file. Regardless of whether an exception occurs or not, it’s important to close() the file.



Edit Get your hands dirty!

This program computes the divisions among the elements of two arrays of integers. Unfortunately, one array contains 0 and the program will throw an exception at line 10:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at exception.Main.main(Main.java:12)

Catch the exception ArithmeticException and print “error division by zero” so that the program can continue to print the remaining divisions.






How to deal with multiple exceptions?

Let’s say that in the same method we want to catch both NullPointerException and ArrayIndexOutOfBoundsException.

We can simply do like this:

try {
    // Code block
} catch (NullPointerException e1) {
    // Handle NullPointerException exceptions
} catch (ArrayIndexOutOfBoundsException e2) {
    // Handle ArrayIndexOutOfBoundsException exceptions
}

In this case different code can be executed to handle the different exception types.

If you want to catch different exception with only one catch you can do like this:

try {
    // Code block
} catch (NullPointerException | ArrayIndexOutOfBoundsException e1) {
    // Handle NullPointerException and  ArrayIndexOutOfBoundsException exceptions
}




Exceptions can be caught together by specifying the generic type.

try {
    // code
} catch (RuntimeException e) {
    // Handle all runtime errors
}

In this case, it will catch both NullPointerException ArrayIndexOutOfBoundsException. But it will also catch all Runtime Exceptions!

Edit Warning

Be careful about being too generic in the types of exceptions you catch. You might end up catching an exception you did not know would be thrown. As such, you might hide errors that you are not aware of.






Checked vs Unchecked exceptions

This is the hierarchy of Throwable objects:

All exceptions and errors extend from a common java.lang.Throwable parent class. Only instances of Throwable (and sub-classes) can be thrown and caught.

The exceptions that we have seen so far (NullPointerException, ArrayIndexOutOfBoundsException, and ArithmeticException) extend java.lang.RuntimeException.

Besides RuntimeException, there are other classes that extend java.lang.Throwable. These have a special meaning in Java:

  • java.lang.Error objects correspond to JVM Errors, like OutOfMemoryError.

  • java.lang.Exception objects correspond to System Errors (while java.lang.RuntimeException to Programming Errors).

Understanding the difference between java.lang.Exception and java.lang.RuntimeException is very important! The key difference is that java.lang.RuntimeExceptions are unchecked, while other java.lang.Exceptions are checked.

Unchecked exceptions mean that the Java compiler does not enforce that developers handle them explicitly. In fact, it is up to you to catch them and do something about them – but only if you want.

Checked exceptions mean that the Java compiler enforces that developers must handle them. Alternatively, methods that generate checked exceptions must declare that they (re-)throws them so that the callee method becomes responsible to handle them.

Examples of checked exceptions are java.io.IOException and java.io.FileNotFoundException, which are thrown when files are related to IO (input/output) operations.

For example, if we try to compile this program, we will have the following three compilation errors:

Main.java:7: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
    FileReader fileReader = new FileReader("to_be_read.txt");
                            ^
Main.java:10: error: unreported exception IOException; must be caught or declared to be thrown
            while ((line = bufferedReader.readLine()) != null) {
                                                  ^
Main.java:13: error: unreported exception IOException; must be caught or declared to be thrown
            fileReader.close();
                            ^
3 errors
compiler exit status 1

The problem is that the mentioned method calls can throw checked exceptions.

An easy fix is to declare that the method main can throw the exceptions like this:

public static void main(String[] args) throws FileNotFoundException, IOException {
    ...
}

But this isn’t ideal, as we should write some code that deals with the exceptions.



Edit Get your hands dirty!

Modify the program below to resolve the compilation errors, by handling the exceptions with try and catch.

In the catch section, you should print error messages.

Note that this time, the exception types do not belong to the java.lang package. Therefore, you need to import them like this:

import java.io.FileNotFoundException;
import java.io.FileReader;



You should know that there is a lot of controversies around checked versus unchecked exceptions. For instance, C# does not have checked exceptions. Many developers think that checked Exceptions make the code verbose. Others think that check exceptions are important to enforce programmers handle potential problems.






Create your own exceptions

It is often important to be able to create your own exceptions that are specific to your application. This is because they will be much more informative to developers. Also, you could add helper methods to your exception’s class, to provide more meaningful error information.

There are four steps to define your own exception class:

  1. Choose a meaningful name that self-describes the Exception. For convention, it must end with Exception.

  2. Create a Java file with a public class declaration with that name.

  3. Decide if the exception should be checked or unchecked:

    • If checked, the class must extend Exception,
    • Otherwise (if unchecked), the class must extend RuntimeException.
  4. Define constructor(s) that call into super’s constructor(s).

For example, let’s declare a checked exception that indicates that we are trying to sell a Book that is sold out.

public class InsufficientBookCopies extends Exception {
    private final String bookTitle;

    public InsufficientBalanceException(String bookTitle) {
        super("Trying to sell a sold-out book: " + bookTitle);
        this.bookTitle = bookTitle;
    }

    public String getBookTitle() {
        return bookTitle;
    }
}

Now that we have created an exception, how to throw it at runtime? Like this:

throw new InsufficientBalanceException("Harry Potter and the Philosopher's Stone");

For example:

if(numCopies == 0) {
    throw new InsufficientBalanceException(bookTitle);
}

How to decide if you should define a checked or unchecked exception?

The official Java Documentation provides guidance on when to use checked and exceptions:

“If a client can reasonably be expected to recover from an exception, make it a checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception.”

https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html

For example, before we open a file, we can first check the input file name. If the user input file name is invalid, we can throw a custom checked exception:

if (!isCorrectFileName(fileName)) {
    throw new IncorrectFileNameException("Incorrect filename : " + fileName );
}

This is a recoverable exceptions (the user can provide an alternative or default file name)

However, if the input file name is a null pointer or it is an empty string, it means that we have some errors in the code. In this case, we should throw an unchecked exception:

if (fileName == null || fileName.isEmpty())  {
    throw new NullOrEmptyException("The filename is null or empty.");
}