Exception Handling

Introduction

In java, when you write some code and the syntax is incorrect, the compiler will not allow you to compile the code unless you fix the syntax error. But even if the syntax is correct, some real bad things can happen at runtime. It can happen due to various reasons. For example –

  1. Coding errors made by you.
  2. You are expecting a file inside a directory, but the file doesn’t exist there.
  3. You are trying to write a file, but the disk space is full.
  4. You are trying to divide by zero.
  5. You are processing user input and the input is wrong.

There can be a thousand other reasons. But the point is, no matter how well you write your code, there is always a chance for something bad to happen. When some bad things happen, it stops the execution of a code in a normal way. You can not always prevent those unexpected behaviors from happening. But you can detect that and take necessary actions against it.

What is an Exception?

An exception means a problem or an abnormal condition that stops the execution of a code in a normal way. In java an exception is represented by an object. This object contains information about the error, including its type and the state of the program when the error occurred etc. 

Let’s try to divide an int value by 0 and check what happens.

public class ExceptionDemo {
    public static void main(String[] args) {
        int result = 1/0;
    }
}

Output:

Exception in thread "main" java.lang.ArithmeticException: / by zero
      at ExceptionDemo.main(ExceptionDemo.java:3)

You can see, an ArithmeticException is thrown with an error message and the line where the exception happened. Now the question is, who created this exception?

The answer is JVM. When JVM faces this division by zero, it creates an ArithmeticException object and throws it to inform us that an error has occurred.

You can also throw an exception by yourself. But in this case, JVM did it after detecting an invalid operation.

After a method throws an exception, the java runtime system tries to find someone to handle it. This method first looks at its caller method and that caller method looks at the method from where that method is called and so on until it finds a block of code (exception handler) that can handle the exception.

When an appropriate handler is found, the runtime system passes the exception to the handler. If no exception handler is found, the runtime system terminates the rest of the program abnormally – this is exactly what happened in our code.

How do we know if we have to write an Exception handler?

You write an exception handler when you call a risky method which may throw an Exception at runtime. If that risky method is written by you, you know that the method can throw an exception. But what if the method is written by someone else? How do you know if that method can throw an exception?

The answer is simple – you will see a throws clause in that method’s declaration as shown below.

public class FileInputStream {
    public FileInputStream(String name) throws FileNotFoundException {
        this(new File(name));
    }
}

When the compiler detects you are calling a risky method, it enforces you to take care of it. Unless you take care, the code will not compile.

You may ask – in our last example we got an ArithmeticException. That means that operation was risky. Then why didn’t the compiler force me to take care of it?

The answer is, there is something called RuntimeException and that means … or wait.. let’s look at the Exception class hierarchy.

Exception Hierarchy

As we said earlier, an exception is an object. In Java, there are many predefined Exception classes. Of course we can also create our own. The Exception classes are arranged in a hierarchy, and their position in the hierarchy can affect the way that they should be handled.

  1. All exception and error types are subclasses of class Throwable, which is at the top of the exception class hierarchy.
  2. The Error class and its subclasses represent a serious problem from which you cannot recover. OutOfMemoryError indicates there is no space to create any more objects. StackOverflowError indicates your application stack reaches the maximum limit (may be due to infinite loop).
  3. The Exception class and its subclasses indicate a less serious problem from which you can recover. They can be either checked or unchecked.
  4. The Java compiler forces you to deal with checked exceptions (compile time exceptions). Unless you do that, code will not compile.
  5. The Runtime Exception and its subclasses (unchecked exception) are never checked by the compiler. So, the Java compiler does not force you to deal with that. The Runtime Exception usually indicates the programmer’s error that should not happen.
Why does the compiler not force us to take care of unchecked exceptions?

In our last example, we got an ArithmeticException when we tried to divide by 0. That means that division operation was risky. Then why didn’t the compiler force us to take care of it?

The reason is ArithmeticException or any other unchecked exceptions generally indicates a programmer’s error that should not happen in the first place. You should first check if the divisor is 0 and if yes, you should not allow the division.

Another most common exception is NullPointerException. This can happen when you try to  access an instance field or method by null reference.

public class ExceptionDemo {
    public static void main(String[] args) {
         method1(null);
    }
    
    public static void method1(List<String> list) {
        System.out.println(list.size());
    }
}

Output: Exception in thread "main" java.lang.NullPointerException

In java, as you deal with objects, a very large percentage of your code can throw this exception. If the java compiler forces you to handle those exceptions, you will see exception handlers everywhere and a simple code will be filled with a horrific mess of exception handlers. That is the reason why runtime exceptions are not checked at compile time.

As a developer, if you write sensible code, you should rarely encounter a RuntimeException. For example, in the code above, the NullPointerException can be avoided by either checking for null before we call the size method or we can write our code such that that list can never be null in the first place.

public class ExceptionDemo {
    public static void main(String[] args) {
         method1(null);
         
    }
    
    public static void method1(List<String> list) {
        int size = (list == null) ? 0 : list.size();
        System.out.println(size);
    }
}

Output: 0
Exception handler

As we said earlier, when we call a method that throws checked exceptions, the Java compiler forces you to deal with checked exceptions. To deal with that, we write an exception handler. By exception handler, we meant – we should wrap the risky code in a try/catch block. When you write a try/catch block, the compiler understands you are aware of the risky behavior and you are prepared to deal with that. You write the risky code in the try block. In the catch block, you write something that should be executed when an exceptional situation happens.

try/catch block

As we said, we write the risky code in the try block. The risky method doesn’t mean it will always fail. It means – it may fail. I can succeed as well.

If it succeeds, the code inside the catch block never runs. But if it fails, the rest of the try block is not executed but the code inside the catch block runs.

Let’s call a risky method (that throws an exception) and wrap it inside a try/catch block.

Case 1: The file doesn’t present inside C: drive

try {
    FileInputStream fis = new FileInputStream("C:\\test.txt");
    System.out.println("Reading the file..");
 } catch (FileNotFoundException e) {
    System.out.println("Exception occurred..");
    e.printStackTrace();
}
System.out.println("Done.");

Output:

Exception occurred..
java.io.FileNotFoundException: C:\test.txt (The system cannot find the file specified)
      at java.io.FileInputStream.open0(Native Method)
      at java.io.FileInputStream.open(Unknown Source)
      at java.io.FileInputStream.<init>(Unknown Source)
      at java.io.FileInputStream.<init>(Unknown Source)
      at ExceptionDemo.main(ExceptionDemo.java:8)
Done.

As you can see, as an exception occurred at line 2, the rest of the try block (line: 3) is not executed and the code inside the catch block is executed.

Case 2: The file exists inside C: drive

If the file exists in C: drive, you will see below output.

Reading the file..
Done.

As you can see from the output, the entire catch block is ignored.

finally block

A finally block is where you put some important code that must run regardless of an exception.

  • If the try block succeeds, the finally block is executed after the try block.
  • If the try block fails, the rest of the try block code is skipped and the control goes to try block. After the catch block completes, the finally block is executed.

Let’s understand two special scenarios with an example. Let’s assume, your method returns something at the end of the try block and your catch block also throws an exception.

public class ExceptionDemo {
    public static void main(String[] args) throws IOException {
        System.out.println(readFile("C:\\test.txt"));
    }

    public static String readFile(String filePath) throws IOException {
        try {
            FileInputStream fis = new FileInputStream(filePath);
            System.out.println("Reading the file..");
            return "Success";
        } catch (FileNotFoundException e) {
            System.out.println("Exception occurred..");
            throw new IOException(e);
        } finally {
            System.out.println("Done.");
        }
    }
}

You may expect, if try succeeds, the return statement will immediately return the value and finally will not be executed. But that is not true. The code inside the finally block will be executed just before returning the value. So, you will see an output –

Reading the file..
Done.
Success

Now, if the try block fails, the control will go to the catch block. Inside the catch block we are again throwing an exception. Here also you may expect, the finally block will not be executed. But that is not true. The code inside the finally block will also be executed. So, you will see an output similar to below –

Exception occurred..
Done.
java.io.IOException: java.io.FileNotFoundException: C:\test1.txt (The system cannot find the file specified)
	at ExceptionDemo.readFile(ExceptionDemo.java:17)
	at ExceptionDemo.main(ExceptionDemo.java:7)

So, please say with me – no matter what, the finally block will always be executed.

Is finally block optional?

Yes, the finally block is optional and will be executed regardless of what happens within the try block. So, this block provides a way to write cleanup clean up code in a single place. That cleanup operation may be closing a file, closing a database connection etc.

Can we have a try block without a catch block?

When you write a try block without a catch or finally block, that is nothing different than not having that try block because you are not handling the exception or cleaning up the resource. So, this is not allowed.

public void readFile(String filePath) {
    try {
        FileInputStream fis = new FileInputStream(filePath);
    }
}

The above code will not compile. When you have a try block, you should have corresponding one or multiple catch block or a finally block. So, you can have a try block with finally without a catch block. So below code will work –

public void readFile(String filePath) throws FileNotFoundException {
    try {
        FileInputStream fis = new FileInputStream(filePath);
    } finally {
        System.out.println("Some cleanup operation");
    }
}

You may ask, is this really that useful as we are not handling the exception? I hope you have noticed we have added a throws clause in method declaration when we have used try with finally. If any of the code in the try block throws a checked exception and you don’t have a catch block to handle that, then that exception must appear in the throws clause of the method declaration. Essentially you are saying – I don’t want to handle the exception, rather I want to delegate that responsibility to the caller method.

But you may want to perform some cleanup operation (like closing database connection) irrespective of any exception. You can put that code in the finally block. So, try with finally means – call a risky method, do not handle the exceptional situation yourself but perform the cleanup operation.

Delegating Responsibility

When we do not want to handle an exception in our method, we can delegate the responsibility to the calling method. We do this by adding a throws clause to our method declaration.

public void readFile(String filePath) throws FileNotFoundException {
    FileInputStream fis = new FileInputStream(filePath);
}

A method can throw more than one exception. So below code is legal –

public void readFile(String filePath) throws FileNotFoundException, ClassNotFoundException {
    someRiskyMethod();
}

If any exception is thrown by a method, that method will just pass on the exception to its caller. That caller method may also choose to throw that exception as well. If any method in the call hierarchy catches the exception, it will be handled there. If nobody explicitly handles the exception, the JVM will eventually catch it and terminate the program.

Handle multiple exception

Let’s assume a method that you are calling is throwing both FileNotFoundException and IOException.

public void aMethod() throws FileNotFoundException, IOException {
    // Some statement
}

When we call this method, we can wrap that call inside try block and write multiple catch blocks one after another specifying the type of error to be handled.

try {
    aMethod();
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

Please note, only one catch block (the will be matched first) will be evaluated. If the exception type is FileNotFoundException, the first catch block will be executed. But if the exception type is IOException, the second catch block will be executed.

You may think, as FileNotFoundException is a child class of IOException, if we catch the IOException only (remember polymorphism), that catch block will be able to handle both the exceptions. Or even better, catch the Exception as all exceptions are child classes of the Exception class.

try {
    aMethod();
} catch (Exception e) {
    e.printStackTrace();
}

The above code will work properly. But try to avoid this. Because, most of the time you may want to catch and recover from separate exceptions differently. For example, if a drive car method throws a PunctureException, you will visit a mechanic. But if that method throws an OutOfFuel exception, you will visit a petrol pump. So, try not to catch all exceptions in a single catch block.

Also remember, the order of the catch blocks is important. A more specific exception must be caught before its parent class. Otherwise the compilation will fail. So, below code will not compile –

try {
    aMethod();
} catch (IOException e) {
    e.printStackTrace();
} catch (FileNotFoundException e) {
    e.printStackTrace();
}

It will throw the following compilation error at the highlighted line: Unreachable catch block for FileNotFoundException. It is already handled by the catch block for IOException.

The reason is, as the IOException is a superclass of FileNotFoundException, if aMethod() throws FileNotFoundException, that will be caught by the first catch block. So, the second catch block is unreachable. So, the compiler will prevent you from making such mistakes. However, if you are dealing with exceptions that are in the same hierarchy, you can place them in any order.

Frequently used methods of exception object
  • getMessage() – returns a short description describing the Exception.
  • printStackTrace() – displays the sequence of method calls when the exception occurred. This is very useful for debugging purposes.

To see it in action, let’s execute below code and do not keep the file in the expected directory to fail the try block.

try {
    FileInputStream fis = new FileInputStream("C:\\test2.txt");
    System.out.println("Reading the file..");
 } catch (FileNotFoundException e) {
    System.out.println(e.getMessage());
    e.printStackTrace();
}

Output:

C:\test2.txt (The system cannot find the file specified) ⇐ message
java.io.FileNotFoundException: C:\test2.txt (The system cannot find the file specified)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(Unknown Source)
	at java.io.FileInputStream.<init>(Unknown Source)
	at java.io.FileInputStream.<init>(Unknown Source)
	at ExceptionDemo.main(ExceptionDemo.java:8)
Custom Exception

You can also create your own exception for your specific needs. 

  • If you want to create an unchecked exception, you have to extend the RuntimeException class.
  • If you want to create a checked exception, you have to extend the Exception class.

Let’s consider the voting age is 18 and if you try to cast your vote before that age, you want to throw a checked exception: InvalidAgeException.

So, first create the custom exception class.

public class InvalidAgeException extends Exception {
    public InvalidAgeException(int age) { 
        super("Age is not valid to cast a vote: " + age);  
    }
}

We have just called the Exception class constructor and passed an error message. That’s it.

Now, in your code, you can check the age and if it’s below 18, you can just throw this exception.

public void castVote(int age) throws InvalidAgeException {
    if (age < 18) {
        throw new InvalidAgeException(age);
    } else {
        System.out.println("You are eligible");
    }
}

If you call this method with invalid age, you will get an InvalidAgeException.

try {
    castVote(15);
} catch (InvalidAgeException e) {
    e.printStackTrace();
}

Output:

InvalidAgeException: Age is not valid to cast a vote: 15
        at ExceptionDemo.castVote(ExceptionDemo.java:12)
        at ExceptionDemo.main(ExceptionDemo.java:4)

That’s it for now. Hope you have enjoyed this tutorial. If you have any doubt, please ask in the comment section. I will try to answer that as soon as possible. Till then, bye bye.