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.
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.
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-calledunchecked
exceptions - inherit from the class
Exception
, the so-calledchecked
exceptions.
Code handling may be slightly different depending on which group the exception belongs to.
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 optionallyfinally
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 * acatch
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");
}
}
}
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 thecatch
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 acatch
block and/or afinally
block.NOTE: The
finally
block will execute even if you exit the method withreturn
in thetry
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
.