Exception Handling is one of the most important aspect of Java programming language. There is no application without errors. Errors happen for various reasons, for example, out of memory, a remote server not responding, a file that is being attempted to open is not present, a duplicate record insert statement fired on a database that has a primary key, trying to read a non-existent array element and many more.
Error and Exception
Java separates errors into two categories, Error and Exception, both having respective classes. As per the documentation, an Error illustrates serious problems that a reasonable application should not try to catch. Exception illustrates conditions that a reasonable application might want to catch.
What they meant by catching? Both Error and Exception extend Throwable class. JVM can throw instances of Throwable class in case of exceptional scenarios. In Java, both Error and Exception are exceptional situations that break the normal flow of an application. When an exceptional situation arises, instead of breaking the application, Java creates an instance of Error or Exception object comprising the error details and throws it to your program. So you can catch that instance and determine what to do in that scenario. But how to catch a Throwable object?
try {
// Here goes the code that can throw Error or Exception
} catch (Throwable throwable) {
// Handle the error or exception
}
As depicted in the above code snippet, we write code that is likely to result in error inside the try block. If JVM encounters any error inside the try block, it creates an instance of type (or sub-type of) Throwable
and throws it using the throw
keyword, which gets caught by the catch block.
You can have multiple catch blocks as below to catch the Error or Exception object using the respective catch blocks.
try {
// Here goes the code that can throw Error or Exception
} catch (Exception ex) {
// Handle the exception
} catch (Error err) {
// Handle the error
}
We do not handle an Error; JVM throws them only when there is a serious problem and usually an application cannot recover from it. However, frequently we handle the occurrences of Exception.
Types of Exceptions
There are 2 types of Exceptions present: (1) Checked Exceptions (2) Unchecked Exceptions.
If a User defined exception extends RuntimeException it is called as unchecked exception, however if that user defined exception extends the Exception class, it will be called as checked exception.
Checked Exception
The compiler checks checked Exceptions during the compilation process. When the compiler finds that a particular line can throw a checked exception, it will show a compilation error and forces the developer to handle that exception explicitly. Checked exceptions are the exceptions derived from Exception class. Below is an example of Exception Handling of Checked Exceptions.
package com.techstackjournal.exceptions;import java.io.File;import java.io.FileReader;public class ExceptionHandlingExample1 { public static void main(String[] args) { FileReader fr = new FileReader(new File("file.txt")); }}
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Unhandled exception type FileNotFoundException
at com.techstackjournal.exceptions.ExceptionHandlingExample1.main(ExceptionHandlingExample1.java:10)
We are getting a compile error at line number 10. It says that there is an unhandled exception of type FileNotFoundException. You must wrap that line within try block and catch FileNotFoundException.
How a compiler comes to know if a line throws exception or not? It is simple, the calling method or constructor must be indicating that they throw an exception with a throws clause in their declaration.
In the above example, we are getting the compilation error due to the FileReader constructor. If you have a look at its signature, you’ll come to know that it throws a FileNotFoundException
, so we must handle it before instantiating FileReader.
public FileReader(File file) throws FileNotFoundException {
super(new FileInputStream(file));
}
Unchecked Exception
Unchecked exceptions are those exceptions that are unchecked by the compiler during the compilation but are thrown at runtime if that exceptional situation occurs. All Error and RuntimeExceptions fall under this category.
package com.techstackjournal.exceptions;
public class ExceptionHandlingExample2 {
public static void main(String[] args) {
int[] arr = { 10, 20, 30 };
System.out.println(arr[5]);
}
}
The above program will not give any compilation error, even though we are trying to access the array element which doesn’t exist. But when you run this code, you’ll get below exception that suggests that the index that we are passing to the array is out of bounds.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at com.techstackjournal.exceptions.ExceptionHandlingExample2.main(ExceptionHandlingExample2.java:7)
Exception Handling by Catching the Exception
- Now we know that JVM throws Exception objects and we need to wrap the code within try-catch blocks to catch those exception objects
- A
try
block may throw multiple exceptions and you can write multiplecatch
blocks next totry
, to catch all of them - Within the catch block, you usually log why there was an exception and continue to recover from it
- When there is an exception in the try block, the control is transferred to the relevant catch block, and the subsequent lines of code in the try block will never get executed
- Once the catch block is executed, control will move to the next executable statement in the stack, instead of going back to the line from where the exception has been thrown
- In case of an exception within a try block, only one relevant catch block gets executed, even though that try block has multiple catch blocks to handle multiple exceptions
- For example, if there is a try block exception handling with FileNotFoundException and ArrayIndexOutOfBounds catch handlers, only one of them will be executed based on the exception occurred and another catch block is just ignored
Finally Block
- Immediately after the
try-catch
block we can write afinally
block - It gets executed always irrespective of whether or not there is an exception
- We usually use it for cleanup activities.
package com.techstackjournal.exceptions;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample3 {
public static void main(String[] args) {
String status = readFile();
switch (status) {
case "FILE_READ_COMPLETE":
System.out.println("Show success screen");
break;
case "FILE_NOT_FOUND":
System.out.println("Show error screen");
break;
}
}
private static String readFile() {
FileReader fr = null;
try {
fr = new FileReader(new File("file.txt"));
return "FILE_READ_COMPLETE";
} catch (FileNotFoundException e) {
System.out.println("File not found");
return "FILE_NOT_FOUND";
} finally {
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
System.out.println("Error closing the file");
}
}
}
}
Why don’t we write the cleanup code in the catch block itself? If you encounter a different exception other than what you are handling, none of the catch blocks will get executed, resulting in a non-execution of the cleanup code. Also, if you start writing cleanup code in a catch block, you will soon end up duplicating the code in multiple catch blocks. Someone may also miss adding that cleanup code to the new catch blocks which they are adding at a later stage. So, in short, if you put code cleanup in the catch block, it adversely affects the code maintainability.
The sequence of catch and catching polymorphically
Similar to methods and constructors, a catch block can also receive exception objects of a class and its sub-types polymorphically. That means, you can handle all exceptions using a single catch block with Exception class, which is a bad exception-handling technique. You may add the parent class exception class as the last resort after handling all the specific exceptions possible in that try block.
While adding multiple catch blocks, you must handle them in child to parent sequence, that is most specific exception to broad level exception. If you try adding broad level exception first, you will get a compilation error saying the granular exceptions are unreachable.
package com.techstackjournal.exceptions;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample4 {
public static void main(String[] args) {
String status = readFile();
switch (status) {
case "FILE_READ_COMPLETE":
System.out.println("Show success screen");
break;
case "FILE_NOT_FOUND":
System.out.println("Show file not found screen");
break;
case "ERROR_OCCURED":
System.out.println("Show error screen");
break;
}
}
private static String readFile() {
FileReader fr = null;
try {
fr = new FileReader(new File("D:\\file.txt"));
System.out.println("First letter: " + (char) fr.read());
return "FILE_READ_COMPLETE";
} catch (FileNotFoundException ex) {
System.out.println("File not found");
return "FILE_NOT_FOUND";
} catch (IOException ex) {
System.out.println("IO exception occured");
return "ERROR_OCCURED";
} catch (Exception ex) {
System.out.println("Something wrong");
return "ERROR_OCCURED";
} finally {
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
System.out.println("Error closing the file");
}
}
}
}
I’ve updated the previous code to add new catch blocks with IOException and then Exception. If specific exception FileNotFoundException or IOException is thrown, respective catch block will handle it. But if there is any other exception, catch with Exception block will handle it.
Propagating Exceptions
Allowing exceptions to spill over to the calling method by not handling it, is called as propagating of exceptions.
You don’t have to handle each exception by suppressing the exception details, especially when you are creating APIs for others to consume. By propagating the exception, you are giving control to the calling application to handle the exception in their preferred way.
But wait, if you don’t handle a checked exception, your application will not even get compiled. This can be addressed by the declaration of exceptions in the method signature.
Declaration of Exceptions in Method Signature
When you don’t want to handle exceptions, you can declare those exceptions in the method declaration, passing the buck to the caller.
private static String readFile() throws FileNotFoundException, IOException {
// Code
}
This way, the compiler knows that your method may throw exceptions and it will check if calling application is handling them.
Now calling application has 2 options to deal with the exception. (1) wrap the method call with try-catch
(2) declare the exceptions in its method to propagate to its calling method.
package com.techstackjournal.exceptions;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample5 {
public static void main(String[] args) {
try {
char firstChar = readFile("D:\\file.txt");
System.out.println("First letter: " + firstChar);
System.out.println("Show success screen");
} catch (FileNotFoundException e) {
System.out.println("Show file not found screen");
} catch (IOException e) {
System.out.println("Show error screen");
}
}
private static char readFile(String filePath) throws FileNotFoundException, IOException {
FileReader fr = new FileReader(new File(filePath));
char firstChar = (char) fr.read();
fr.close();
return firstChar;
}
}
I’ve updated our previous example by declaring exceptions in readFile
method signature to propagate exceptions to the calling method. JVM will raise an exception, in case the given filePath
is not found. But the calling method main
handles that exception by surrounding the readFile
method call within try-catch
block.
If you want to handle the checked exception, wrap it inside try-catch, else declare all the checked exceptions that the method will throw.
Throwing Exceptions
Till this point, we are catching the exceptions that are being thrown by JVM. JVM uses throw keyword to throw an Exception when an exceptional situation occurred. Similarly, we can also throw exceptions based on the situation.
In this sample program, we want to verify whether the email contains @ symbol or not. If @ symbol is present all is good, but if @ symbol is not present we want to throw an exception.
package com.techstackjournal.exceptions;
public class ExceptionHandlingExample6 {
public static void main(String[] args) {
try {
parseEmail("admintechstackjournal.com");
System.out.println("Email parsed successfully");
} catch (Exception e) {
e.printStackTrace();
}
}
private static void parseEmail(String email) throws Exception {
if (email.indexOf("@") == -1) {
throw new Exception("Invalid email");
}
}
}
You can see that when @ symbol is not present, we are instantiating the Exception and throwing that instance using the throw
keyword. General practice is that we throw custom exceptions in this kind of situation instead of throwing API provided exceptions.
java.lang.Exception: Invalid email
at com.techstackjournal.exceptions.ExceptionHandlingExample6.parseEmail(ExceptionHandlingExample6.java:15)
at com.techstackjournal.exceptions.ExceptionHandlingExample6.main(ExceptionHandlingExample6.java:7)
Custom Exception
By extending from an Exception
or any subclass of Exception
class, we can create custom exceptions. If you derive the new exception from Exception
class, since Exception
class is a checked exception, the new custom exception class will also be a checked exception. If you derive the new exception from RuntimeException
class, it will be an unchecked exception.
package com.techstackjournal.exceptions;
public class InvalidEmailException extends Exception {
public InvalidEmailException(String email) {
super("Invalid email: "+email);
}
}
Our custom exception class inherit methods such as getMessage
and printStackTrace
from the super class Throwable
.
package com.techstackjournal.exceptions;
public class ExceptionHandlingExample8 {
public static void main(String[] args) {
try {
parseEmail("admintechstackjournal.com");
System.out.println("Email parsed successfully");
} catch (InvalidEmailException e) {
e.printStackTrace();
}
}
private static void parseEmail(String email) throws InvalidEmailException {
if (email.indexOf("@") == -1) {
throw new InvalidEmailException(email);
}
}
}
Similar to our earlier example, we are throwing our custom exception InvalidEmailException
when email doesn’t contain the @ symbol.
Horrors of Catching and Throwing Exceptions
I saw many programmers handle exceptions using catch block, log the exception and throw a custom application exception. This is such a poor practice that it will create a nightmare situation if there is an exception in production. For troubleshooting, if you look at the logs, you will only see the line number from where the custom exception is being thrown. We will lose the original exception details with this approach.
package com.techstackjournal.exceptions;
public class ApplicationException extends Exception {
public ApplicationException() {
super("Unexpected problem occurred");
}
}
package com.techstackjournal.exceptions;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample9 {
public static void main(String[] args) {
try {
char firstChar = readFile();
System.out.println("First letter: " + firstChar);
} catch (ApplicationException e) {
e.printStackTrace();
}
}
private static char readFile() throws ApplicationException {
FileReader fr = null;
char firstChar;
try {
fr = new FileReader(new File("D:\\file2.txt"));
System.out.println(fr);
firstChar = (char) fr.read();
fr.close();
} catch (IOException ex) {
System.out.println("File not found");
throw new ApplicationException();
}
return firstChar;
}
}
In the above program, we are throwing ApplicationException if JVM throws FileNotFoundException. When “file1.txt” is not present, we will get below output.
File not found
com.techstackjournal.exceptions.ApplicationException: Unexpected problem occurred
at com.techstackjournal.exceptions.ExceptionHandlingExample9.readFile(ExceptionHandlingExample9.java:28)
at com.techstackjournal.exceptions.ExceptionHandlingExample9.main(ExceptionHandlingExample9.java:11)
It suppresses the original source of the exception at Line number 22.
Correct way of Throwing Custom Exceptions
Do not suppress the original exceptions thrown by JVM with custom exception.
If you had to suppress the original exception with your custom exception, prefer passing the original exception as cause to the new exception.
package com.techstackjournal.exceptions;
public class ApplicationException extends Exception {
public ApplicationException(Throwable t) {
super(t);
}
}
package com.techstackjournal.exceptions;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample9 {
public static void main(String[] args) {
try {
char firstChar = readFile();
System.out.println("First letter: " + firstChar);
} catch (ApplicationException e) {
e.printStackTrace();
}
}
private static char readFile() throws ApplicationException {
FileReader fr = null;
char firstChar;
try {
fr = new FileReader(new File("D:\\file1.txt"));
System.out.println(fr);
firstChar = (char) fr.read();
fr.close();
} catch (IOException ex) {
System.out.println("File not found");
throw new ApplicationException(ex);
}
return firstChar;
}
}
In Java, the cause of an Exception can be because of another Exception. The super class Throwable contains an attribute called cause
, which is nothing but another Throwable. This signifies the cause of the current Exception.
In the above program, we are passing the original exception as a cause to the ApplicationException
constructor. When we follow this approach, the original exception will not get suppressed instead it will be printed in logs as the cause of the ApplicationException as below.
File not found
com.techstackjournal.exceptions.ApplicationException: java.io.FileNotFoundException: D:\file1.txt (The system cannot find the file specified)
at com.techstackjournal.exceptions.ExceptionHandlingExample9.readFile(ExceptionHandlingExample9.java:28)
at com.techstackjournal.exceptions.ExceptionHandlingExample9.main(ExceptionHandlingExample9.java:11)
Caused by: java.io.FileNotFoundException: D:\file1.txt (The system cannot find the file specified)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileReader.<init>(FileReader.java:72)
at com.techstackjournal.exceptions.ExceptionHandlingExample9.readFile(ExceptionHandlingExample9.java:22)
... 1 more
If the intention is to log the exception before you want to throw it back to the calling application, you can throw the original exception itself starting from Java 7.
Handle and Re-throw an Exception
Java allows you to re-throw an exception from within the catch block to the calling method. To re-throw an exception, you do not have to use the new keyword. You just need to throw whatever object you received in the catch block.
Since we are re-throwing exceptions, we need to declare those exceptions in the method signature
The beauty of doing this is, you can perform any post exception activities and re-throw it to the calling method. Before Java 7, we used to have only one option either to handle it or to throw it.
Best thing is, the stack trace still shows the original line number where the exception has occurred
package com.techstackjournal.exceptions;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample7 {
public static void main(String[] args) {
try {
char firstChar = readFile();
System.out.println("First letter: " + firstChar);
} catch (IOException e) {
e.printStackTrace();
}
}
private static char readFile() throws IOException {
FileReader fr = null;
char firstChar;
try {
fr = new FileReader(new File("D:\\file1.txt"));
System.out.println(fr);
firstChar = (char) fr.read();
fr.close();
} catch (IOException ex) {
System.out.println("IO exception occured");
throw ex;
}
return firstChar;
}
}
In the above program, readFile
method is catching FileNotFoundException
and IOException
, but after printing the error message we are throwing them back to the calling program.
So, when file1.txt doesn’t exist, we get below output:
IO exception occured
java.io.FileNotFoundException: D:\file1.txt (The system cannot find the file specified)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileReader.<init>(FileReader.java:72)
at com.techstackjournal.exceptions.ExceptionHandlingExample7.readFile(ExceptionHandlingExample7.java:23)
at com.techstackjournal.exceptions.ExceptionHandlingExample7.main(ExceptionHandlingExample7.java:12)