Skip to content

Exceptions

Introduction

An exception is an event that occurs during the execution of a program and breaks the normal process of its operation. After an exception is thrown, the runtime tries to find a piece of code that can handle it. The set of possible elements for handling an exception is an ordered list of methods that were called to get to the method where the error occurred. The method list is known as a stacktrace.

Java Exception

The system searches the call stack at run time for a method that will handle the exception. The search begins with the method in which the exception was thrown and ends with the method that is responsible for handling the event.

Java Exception Handler

Exception types

In Java, all exceptions can be divided into two main groups. These groups contain exceptions that:

  • inherit from the class RuntimeException, the so-called unchecked exceptions
  • inherit from the class Exception, the so-called checked exceptions.

Code handling may be slightly different depending on which group the exception belongs to.

Java Throwable

NOTE: Each exception inherits from the Throwable class.

Catching and handling exceptions

Exceptions and method signature

If calling a method throws a checked exception, we can do this:

  • do not handle and pass "up" (i.e., prop the exception up, according to the call stack)
  • handle within a method.

To pass on the need to handle exceptions, we need to add the throws keyword in the signature of the method, followed by the list of exception types that this method can throw. This behavior is illustrated by an example:

  public void sleepAndOpenFile(final String filePath) throws InterruptedException, FileNotFoundException { // (1)
    Thread.sleep(10); // (2)
    new FileReader(filePath); // (3)
  }

Calling the method on the (2) line may throw an InterruptedException exception, while the constructor of theFileReader class may throw an FileNotFoundException exception. We choose not to handle these exceptions in our method, so we add this information to the method signature on line (1). This means that the programmer calling the sleepAndOpenFile method will also have to decide what to do with (handle or forward) these exceptions.

In order to handle the exception, i.e. to do something about it, we need to use certain keywords:

  • try along withcatch and optionally finally
  • try with uploading resources.

try and catch

The first step towards exception handling is declaring a try block along with acatch block. In the try block we define code that can throw exceptions of a certain type, then in thecatch block that follows immediately after the try block we define the code that should be executed when an exception occurs. The type of the exception and its instance are given inside the parentheses, right after the catch keyword, e.g .:

try { // (1)
  Thread.sleep(100L); // (2)              
} catch (InterruptedException e) { // (3)
  e.printStackTrace(); // (4)
}

In the above example, on line (1), we define the try code block. In this block, we call a method that can throw a checked exception (i.e. inherit from theException class). We handle this exception (InterruptedException) in line(4), printing the current call stack to the screen. This code is defined in the catch block (on the(3)line).

We may add multiple catch blocks to a single try block. Each of the catch blocks can handle a different type of exception, such as:

try {
  Thread.sleep(100L);
  new RestTemplate().getForEntity("http://localhost:8080", Object.class);
} catch (RestClientException e) {
  System.err.println("Error occurred from RestTemplate object");
} catch (InterruptedException e) {
  e.printStackTrace();
}

While creating catch blocks, we must remember a few facts:

  • we can catch any unchecked exception
  • we can only catch checked exceptions which may be thrown within atry block
  • we can catch a type that is a subclass of the exception type, i.e. if exception B inherits from class A, then instead of catching type B, we can catch type A
  • we can catch exceptions that inherit from each other, but catch blocks with a more specific exception must appear first, i.e. if class B inherits from class A, then thecatch that handles type B must be found * before * a catch block that handles type A.

public class CatchHierarchy {
  public static void main(String[] args) {
    try {
      final KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
      final Cipher cipher = Cipher.getInstance("AES");
    } catch (NoSuchAlgorithmException e) {
      System.err.println("Incorrect algorithm name");
    } catch (NoSuchPaddingException e) {
      System.err.println("Oops");
    } catch (Exception e) {
      System.err.println("Generic exception occurred");
    }
  }
}
In the example above, the most generic type of exception, i.e. Exception, is caught at the very end. Replacing types, e.g. NoSuchAlgorithmException withException in the appropriate catch blocks, would result in a compile-time error.

What if we want to handle many types of exceptions in exactly the same way? We don't need to define multiple catch blocks for this. One is enough, and the list of exceptions that we want to handle with the same code is separated with the | character, e.g .:

public class MultiExceptionsSingleCatch {
  public static void main(String[] args) {
    try {
      final KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
      final Cipher cipher = Cipher.getInstance("AES");
    } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
      e.printStackTrace();
    }
  }
}

NOTE: Using the | character in the catch block, not we can specify two classes from one hierarchy (i.e. one of them is derived from the other). It will result in a compilation error, e.g. catch (Exception | RuntimeException e).

finally

Applications operate with a finite amount of resources (e.g. memory, maximum number of files, or database connections). If some resources were opened during code execution and then an exception was thrown, we should always close them anyway. This mechanism provides a finally block, which is invoked whenever there is atry block **, whether or not an exception is thrown. Block finally is always at the end oftry / catch blocks.

The following example shows how to close resources with a finally block:

public class FinallyExample {
  public static void main(String[] args) throws IOException {
    BufferedReader bufferedReader = null;
    try {
      bufferedReader = new BufferedReader(new FileReader("/tmp/sda.txt"));
    } catch (FileNotFoundException e) {
      System.exit(1);
    } finally {
      if (bufferedReader != null) {
        bufferedReader.close();
      }
    }
  }
}

NOTE: A try block requires a catch block and/or a finally block.

NOTE: The finally block will execute even if you exit the method withreturn in the try block.

try with a resource

It is also possible to pass a resource list to the try block. Using a try block in this way frees you from manually closing resources in afinally block. Such a resource can be any object that implements java.lang.AutoCloseable or that implementsjava.io.Closeable.

The next example uses the fact that the BufferedReader class implements theCloseable interface:

public String readFirstLineFromFile(String path) {
  try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
  } catch (IOException e) {
    return "FAILED";
  }
}

Throwing exceptions

In the source code, in addition to catching already reported exceptions, it is possible to throw exceptions. The exception must be derived from the Throwable class. We use the throw clause to throw exceptions, e.g.:

public void validateAddress(String address) {
  if (address == null || address.isEmpty()){
    throw new IllegalArgumentException("illegal address");   
  }
  // do some operations
}

Creating your own exceptions

Java provides many exception classes that you can use. You can also create your own exceptions. If any of the following criteria are met, consider preparing your own type:

  • there is a need to have an exception that is not represented by any available exception type
  • the exception used in the code should be different from the exceptions of other providers.

The following examples make their own exceptions:

public class SdaException extends RuntimeException {
  public SdaException(final String message, final Throwable cause) {
    super(message, cause);
  }
}
public class IllegalAddressException extends IllegalArgumentException {

    public IllegalAddressException(final String address) {
        super(String.format("Provided address %s is not valid!", address));
    }
}

Error

Error exceptions are special types of exceptions. We should not try to handle them in any way (i.e. we should not use try and catch block or use throws in method signature). When such an exception occurs we are unable to do anything about it from application level. Their appearance usually terminates the application. Example of such exception is OutOfMemoryError.