Java Exception Handling

In this tutorial, we’ll look at the basics of exception handling in Java.

Java Exception Handling Introduction

Exception handling in Java provides a means to handle errors within our code gracefully. If Java did not provide an exception handling mechanism our program may not be able to complete the tasks it was designed for, it may crash and become unavailable.

To better understand java exceptions consider the following. Imagine a pilot is flying passengers on a long haul flight from A to B and our pilot is overworked by the airline. The pilot breaks protocol and puts the plane on autopilot and decides to go for a sleep. Suddenly the pilot is awakened by an alarm indicating the autopilot has failed and the plane requires the pilot to take immediate control. In this case the pilot takes control and averts a catastrophe.

In the same way Java exception handling is used to handle unexpected situations so the program can recover and carry out its tasks as expected.

What is Java Exception Handling

Exception Handling is a mechanism to handle runtime errors within our programs. These runtime errors can be caused by technical / infrastructure issues reported by the Java runtime such as a networking issue, out of memory issue, looking up a resource that may throw Java Exceptions such as: IOException, SQLException, OutOfMemoryError.

Java Exception Handling may also be required to handle custom exceptions related to the business logic of a Java application. Java exception handling is used to maintain the expected flow of an application by recovering from unexpected situations. Let’s consider a simple example imagine our Java program facilitates configuring itself via a configuration file and has a method used to configure the application.

1
2
3
public void configure() {
    File configurationFile =  ResourceUtils.getFileFromResources("appConfiguration.yml");
}

The configure method searches for the file appConfiguration.yml in order to configure itself however if the file cannot be read maybe due to a the file system becoming corrupt the application will fail to start and report the exception:

1
2
3
4
Exception in thread "main" com.stephenenright.tutorials.corejavaexception.utils.ResourceUtils$ResourceNotFoundException: File Not Found
    at com.stephenenright.tutorials.corejavaexception.utils.ResourceUtils.getFileFromResources(ResourceUtils.java:14)
    at com.stephenenright.tutorials.corejavaexception.exception.AppConfiguration.configure(AppConfiguration.java:15)
    at com.stephenenright.tutorials.corejavaexception.exception.AppConfiguration.main(AppConfiguration.java:11)

Now, instead of the application failing we would like to employ java exception handling to handle the unexpected scenario of the configuration file not being available and resume normal operation by using a default configuration.

1
2
3
4
5
6
7
try {
    File configurationFile =  ResourceUtils.getFileFromResources("appConfiguration.yml");
}
catch(ResourceNotFoundException rnfe) {
    Logger.error("Unable to apply configuration. Applying default configuration", rnfe);
    applyDefaultConfiguration()
}

In the above code we use java exception handling to apply the default application configuration and ensure our application is available to perform it tasks.

Java Exception Hierarchy

The Java exception hierarchy is divided into two many types of errors. These are Errors and Exceptions. Errors are used to indicate a serious application error, a problem that an application should not try to recover from such as an OutOfMemoryException.

Java Exception Hierarchy The java exception hierarchy also provides the exception base type which indicates errors that the application may want to recover from. Java exception and Java errors types are sub classes of the Throwable class, which is base class of the java exception hierarchy. The side of the hierarchy that has the Exception as an ancestor is for errors that a program should probably catch and recover from such as an IOException, NullPointerException etc. The classes that have Error as an ancestor are used to indicate errors that have to do with the runtime environment, and the application is not probably able to recover from it so it should not be handled by java exception handling.

We can further divide the Java exception class in two main categories of exceptions

  • Checked exceptions
  • Unchecked exceptions

Unchecked exceptions are normally referred to as Runtime Exceptions.

Checked Exceptions

Checked Exceptions are the exceptions that are checked at compile time. If code within a method throws a checked exception then we must handle the exception or declaratively throw the exception up the call stack by declaring it using the throws keyword in the method signature.

Unchecked Exceptions

Unchecked exceptions are exceptions that extend RuntimeException. These are exceptions that the Java compiler does not explicitly require us to handle. However, if we don’t handle them at some point within our code they can propagate up the call stack and terminate the program.

Errors

As mentioned earlier Errors represent serious problems which are usually irrecoverable conditions memory leaks sometimes related to the environment in which are code runs such as the OutOfMemoryError and StackOverflowError. Errors are problems that are generally classed as not recoverable and therefore we should normally not handle them within our code.

Java Exceptions And The JVM

When a Java Exception has occurred inside a Java Method the method creates an object known as a Java Exception Object and hands it off to the JVM. When this Exception Object is instantiated it is created with various bits of information such as its name and description. This information is useful as we can identify the type of the Java Exception in order to identify and understand why this Exception may have occurred. The Java exception object also contains another important piece of information probably the most important is the call stack, which is a trace of the list of methods that have being called to get to the method where the exception occurred. This listing of method calls is commonly referred to as a stack trace. This information is very important for debugging and finding the root cause of the Exception. Let’s see an example of a stack trace.

1
2
3
4
public File readConfigurationFile() {
    File configurationFile =  ResourceUtils.getFileFromResources("appConfiguration.yml");
    return configurationFile;
}

The above code will throw a ResourceNotFoundException which is a RuntimeException, and if this exception is not handled the program will terminate and we will see a stack trace output to the console.

1
2
3
4
5
Exception in thread "main" com.stephenenright.tutorials.corejavaexception.utils.ResourceUtils$ResourceNotFoundException: File Not Found
    at com.stephenenright.tutorials.corejavaexception.utils.ResourceUtils.getFileFromResources(ResourceUtils.java:14)
    at com.stephenenright.tutorials.corejavaexception.exception.AppConfiguration.readConfigurationFile(AppConfiguration.java:20)
    at com.stephenenright.tutorials.corejavaexception.exception.AppConfiguration.configure(AppConfiguration.java:16)
    at com.stephenenright.tutorials.corejavaexception.exception.AppConfiguration.main(AppConfiguration.java:12)

As we can see in the above code we can see that the java exception was initially created at ResourceUtils.java line number 14. We can also see the list of method calls that got us to that point, with the first method call from the java main method stating at AppConfiguration.java line 12 the last line in the stack trace above. We can use this stack trace to step through the code as we can determine the path the code took until the exception occurred. This information is very useful when we need to perform root cause analysis.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.stephenenright.tutorials.corejavaexception.exception;

import com.stephenenright.tutorials.corejavaexception.utils.ResourceUtils;
import com.stephenenright.tutorials.corejavaexception.utils.ResourceUtils.ResourceNotFoundException;

import java.io.File;

public class AppConfiguration {

    public static void main(String[] args) {
        AppConfiguration configuration = new AppConfiguration();
        configuration.configure();
    }

    public void configure() {
        File configFile = readConfigurationFile();
    }

    public File readConfigurationFile() {
        File configurationFile =  ResourceUtils.getFileFromResources("appConfiguration.yml");
        return configurationFile;
    }
}

Handling Java Exceptions

When we write Java programs there are plenty of places where things can go wrong. In some cases these places have the type of exception declared in the method signature as is the case with checked exceptions, and sometimes these exceptions can be found in the javadoc. If we want our program to respond to these exceptional conditions we must handle them to ensure the smooth running of our program.

To handle Java exceptions we have a number of options available. These options are managed by five main keywords: try, catch, throw, throws, and finally. In general if we want to handle places in our code that we think may raise an exception we can put that code within a try block if the code within a try block results in an exception we can handle that exception by catching it in the catch block and finally if we want to execute some code after an exception is thrown from a try block or after the try block completes as expected we can place that code within the finally block. The throw keyword, allows us to raise exceptions from the code we write, and the throws keywords allows us to declare in the method signature that the code may throw an exception and that the client code is required to do something with it.

Throws

The throws keyword is useful when dealing with checked exceptions. If we don’t handle the checked exceptions from the call site of a method that throws an exception the code will not compile. The simplest way to handle the checked exception is to use the throws keyword in the method signature.

1
2
3
4
5
6
7
8
public static void openFile(String file) throws IOException, FileNotFoundException {
    FileInputStream input = new FileInputStream(file);
    int data = input.read();
    while (data != -1) {
        System.out.print((char) data);
        data = input.read();
    }
}

In the above code the FileNotFoundException is thrown from line 2 and the IOException is thrown from line 3 and 6, if we don’t handle these exceptions the code will not compile. In this case we handle the exceptions by using the throws keyword in the method signature to delegate the responsibility of handling the exceptions to the caller of our openFile method.

Try Catch

We can handle checked and unchecked exceptions ourselves by using the try-catch construct. The first approach to handling the exception in a try-catch is to simply rethrow the exception’s

1
2
3
4
5
6
7
public static void openFile(String file)  {
    try {
        FileInputStream input = new FileInputStream(file);
    } catch(FileNotFoundException fnfe) {
        throw new IllegalArgumentException("File not found", fnfe);
    }
}

In the above code we handle the FileNotFoundException by throwing a new RuntimeException of type IllegalArgumentException. We can also rethrow the same exception that we caught.

1
2
3
4
5
6
7
8
public static void openFile(String file) throws FileNotFoundException {
    try {
        FileInputStream input = new FileInputStream(file);
    } catch(FileNotFoundException fnfe) {
        fileStats.incrementFilesNotFound()
        throw fnfe;
    }
}

Finally, we can handle the exception by performing a recovery step.

1
2
3
4
5
6
7
public void configureApplication(String configFile) {
    try {
        FileInputStream input = new FileInputStream(configFile);
    } catch(FileNotFoundException fnfe) {
        appConfiger.configureDefault();
    }
}

In the above code if the configuration file is not found we handle the exception by applying the default configuration

Finally

As part of our Java exception handling strategy there are times when we would like code to execute regardless of whether an exception occurs. In this case we can use the finally block.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public void readFile() throws IOException {
    FileInputStream is = null;
    try {
        is = new FileInputStream(ResourceUtils.getFileFromResources("file.txt"));
        int data = is.read();
        while (data != -1) {
            System.out.print((char) data);
            data = is.read();
        }
    } finally {
        if (is != null) {
            try {
                is.close();
            } catch (IOException ioe) {
               logger.error("Unable to close file", ioe);
            }
        }
    }
}

The above code shows a common pattern before Java 7 in which the finally block is used. In the above code we use the finally block to close the handle to file and return it to the operating system to clean up the resource after use.

Try With Resources

With the arrival of Java 7 we can simplify the above the code by using the try-with resources construct to automatically have the file handle cleaned after use, without having to write the boilerplate code.

1
2
3
4
5
6
7
8
9
private static void readFile() throws IOException {
    try (FileInputStream is = new FileInputStream(ResourceUtils.getFileFromResources("file.txt"))) {
        int data = is.read();
        while (data != -1) {
            System.out.print((char) data);
            data = is.read();
        }
    }
}

You can read more about try-with-resources here

Multiple Catch Blocks

When handling java exceptions sometimes the code within our try block can throw multiple exceptions and we may want to handle them in the same way. To achieve this we can use the multiple catch block as part of our java exception handling strategy.

1
2
3
4
5
6
7
8
public void readFile() {
    try {
        FileInputStream is = new FileInputStream(ResourceUtils.getFileFromResources("file.txt"));
    } catch (IOException | ResourceNotFoundException e) {
        fileStats.incrementFailedCount();
        throw new FileProcessingException("Error processing file", e);
    } 
}

You can read more about multiple catch blocks here

Throwing Exceptions

Sometimes we don’t want to handle the exceptions ourselves or we want to raise a new exception to tell the client of our code that an exception occurred. In this case we can use the throw keyword.

Throwing Runtime Exceptions

For example lets say that we have a method to read a configuration file and the file name must have the text configuration.yml in this case we want to notify the client of our code of a programming error. We can do this be throwing a RuntimeException of they IllegalArgumentException.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public void readConfigurationFile(String configuationFile) {
    if(configuationFile==null || configuationFile.trim().equals("")) {
        throw new IllegalArgumentException("Configuration file required");
    }

    File configFile = new File(configuationFile); 

    if(!configFile.getName().equals("configuration.yml")) {
        throw new IllegalArgumentException("Configuration file should be named configuration.yml");
    }

    //process file
}

In the above code because the IllegalArgumentException is a RuntimeException we do not have to declare it in the throws clause of the method signature.

Throwing Checked Exceptions

We can also throw a checked exception for a method. If we throw a checked exception from a method unlike throwing a RuntimeException we force the client of our code to handle the exception.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
private static class ResourceNotFoundExceptionChecked extends Exception {
    public ResourceNotFoundExceptionChecked (String message) {
        this(message, null);
    }
    public ResourceNotFoundExceptionChecked (Throwable cause) {
        this(null, cause);
    }
    public ResourceNotFoundExceptionChecked (String message, Throwable cause) {
        super(message, cause);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public static File getFileFromResource(String fileName) throws ResourceNotFoundExceptionChecked  {

    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    URL resource = classLoader.getResource(fileName);
    if (resource == null) {
        throw new ResourceNotFoundExceptionChecked("File Not Found");
    } else {
        return new File(resource.getFile());
    }
}

We must also use the throws clause to indicate that the method throws an exception as shown in the above example.

Rethrowing Exceptions

As part of our exception handling strategy we can rethrow an exception. For example in the below code we update the file statistics and rethrow the exception.

1
2
3
4
5
6
7
8
public void readFile() throws IOException {
    try {
        FileInputStream is = new FileInputStream(ResourceUtils.getFileFromResources("file.txt"));
    } catch (IOException ioe)  {
        fileStats.incrementFailedCount();
        throw ioe;
    }
}

In the above code we rethrow the checked exception of type IOException and as we are rethrowing a checked exception we have to add the IOException to the throws clause of the method signature.

We can also rethrow an unchecked exception in this case we don’t have to add the exception to throws clause of the method signature.

1
2
3
4
5
6
7
public void readFile() throws IOException {
    try {
        File file = ResourceUtils.getFileFromResources("file.txt");
    } catch (ResourceNotFoundException rnfe)  {
        throw rnfe
    }
}

Wrapping Exceptions

We can also wrap exceptions as part of our Java exception handling strategy. In this case we can catch the exception and rethrow the exception wrapping the exception in a new exception type. Again, we have to remember that if we are rethrowing a checked exception we must declare it in the throws clause of the method signature.

1
2
3
4
5
6
7
8
public void readFile() {
    try {
        File file = ResourceUtils.getFileFromResources("file.txt");
        FileInputStream input = new FileInputStream(file);
    } catch (Exception e)  {
        throw new FileProcessingException("Unable to process file", e);
    }
}

It is also important when wrapping an exception that we pass the original exception – the wrapped exception to the exception that we are throwing. This is to aid root cause analysis because if we don’t we will not see the original cause of the exception in the stack trace.

1
throw new FileProcessingException("Unable to process file", e);

Common Java Exceptions And Errors

Checked Exceptions

Some common java check exceptions include:

  • IOException – This exception is normally thrown to indicate that there is a network, filesystem error.

Runtime Exceptions

Some common Java runtime exception include:

  • NumberFormatException – This exception normally means that we tried to convert a String into a number, but the string contained illegal characters.
  • NullPointerException – This exception means we tried to reference a null object. This can be avoided by defensive null checks or by using Optional.
  • llegalStateException – This exception is generic in the sense that we can indicate the internal state of something is invalid.
  • ClassCastException – this exception indicates that we tried to perform an illegal cast, of incompatible class types.
  • ArrayIndexOutOfBoundsException – this exception indicates that we tried to access an array index that was outside the bounds of the array.
  • llegalArgumentException – this exception is usually used to indicate a programming error such as passing an invalid parameter to a method or a constructor.

Errors

Some common errors include:

  • OutOfMemoryError – this exception indicates that the JVM doesn’t have enough memory available to allocate for more objects in the JVM memory, this error could be due to a memory leak.
  • StackOverflowError – this exception indicates that the call stack is to large. This exception normally indicates a programming error or a recursive call that is to deep an exceeds the limits of the JVM.
  • NoClassDefFoundError – This method indicates that a class could not be found this could be due to classpath issues.

Conclusion

We covered the basics of exception handling in Java. We also looked at the Java exception hierarchy and some of the semantics related to Java exception handling in the JVM.