1. Overview

Simply put, an exception is a runtime error that causes abnormal termination of the program. It can be programmatically managed by the programmer using try-catch blocks so that it does not result in abnormal termination.

It is the programmers' responsibility to manage exceptions while writing the code.

Note: An exception can only be handled by the programmer it can never be eliminated completely, For example, there is always a chance of a user trying to divide a number by zero, or a file is not found at the mentioned location. A programmer cannot handle such things he/she can only handle the exceptions that occur in these situations.

Java has defined a Throwable class to represent all the exceptions and errors. It is the super-class of all the exceptions and errors.

Types of throwable in java

2. Difference between exceptions and errors

The main difference between an exception and an error is that exceptions are handled by the programmer while writing code to prevent the program from terminating abnormally whenever an exception occurs whereas, in case of errors, a programmer is not responsible for handling the errors.

Note: Technically an error can be handled using try-catch blocks because an error is a sub-class of throwable but it is not advised because an error means a serious problem where in most cases there is nothing you can do.

3. Default exception handling in java

Java has a default exception handling mechanism that kicks in if an exception occurs and the exception handling is not implemented by the programmer. This default exception handling mechanism does not do much it just prints the stack trace of the exception.

Example:

class Sample{
	public static void main(String arr[]){		
		System.out.println("Dividing a number by 0");
		System.out.println(doSomething());
		System.out.println("This won't be printed");	
	}
	
	public static int doSomething(){
		return 5/0;
	}
}

Output:

Dividing a number by 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Sample.doSomething(Sample.java:11)
	at Sample.main(Sample.java:5)

As you can see in the above code that we did not write any exception handling code, so the default exception handling kicked in when we tried to divide 5 by 0 and all it did was just print the stack trace of the exception.

The above code is equivalent to the code below

class Sample{
	public static void main(String arr[]){
		try{
			System.out.println("Dividing a number by 0");
			System.out.println(doSomething());
			System.out.println("This won't be printed");
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public static int doSomething(){
		return 5/0;
	}
}

4. Handling exception with try-catch

Now we know that java has a built-in default exception handling mechanism. Let's see how we can manually override this mechanism to replace it with our own code. Sometimes default exception handling mechanism is not enough and we want to do something more than just printing the stack trace. In such cases, we use the 'try-catch' blocks. 'try-catch' blocks allow us to handle exceptions however we want. The error-prone code or the risky code which may cause an exception is written inside the try block and code to handle the exception is written inside the catch block. When an exception rises from the code which is written inside the try block the control is transferred to the catch block and code inside the catch block is executed.

Example:

class Sample{
	public static void main(String arr[]){
		try{
			System.out.println("Dividing a number by 0");
			System.out.println(5/0);
			System.out.println("This won't be executed");
		}catch(ArithmeticException e){
			System.out.println("Inside the catch block");
		}
	}

Output:

Dividing a number by 0
Inside the catch block

As you can see in the above code when we tried to divide a number by 0 the control was transferred to the catch block.

Note: It is advised to write only error-prone or risky code inside the try block because there is no guarantee that all lines of code of the try block will be executed.

5. try with multiple catch blocks

We can also use multiple catch blocks with a single try block. We know that a program can throw different exceptions in such cases we may want to handle the different exceptions differently. That is where multiple catch blocks come in handy. Multiple catch blocks are placed to catch different exceptions and to handle them differently. We provide a different catch block for each exception that a program is likely to throw and different error handling code is written inside them.

Example:

class Sample{
	public static void main(String arr[]){
		
		int[] ar = {1};
		
		try{
			System.out.println("Dividing a number by 0");
			System.out.println(ar[0]/ar[1]);
		}
		catch(ArithmeticException e){
			System.out.println("ArithmeticException is caught");
		}
		catch(ArrayIndexOutOfBoundsException e){
			System.out.println("ArrayIndexOutOfBoundsException is caught");
		}
		catch(NullPointerException e){
			System.out.println("NullPointerException is caught");
		}
	}

Output:

Dividing a number by 0
ArrayIndexOutOfBoundsException is caught

An exception is caught only by the corresponding catch block as you can see in the example above.

Note: When using multiple catch blocks make sure to catch exceptions in the reverse order of their hierarchy. That is the more generalized the exception is the lower it should be placed and the more specific the exception is the higher it should be placed.

Frame 4.png

For example, NullPointerException should be caught before RuntimeException and RuntimeException should be caught before Exception.

6. finally block

finally block is also known as the cleanup block. finally block contains the cleanup code for a program. In this block, all the open connections are closed or all the acquired resources are released. try block should be present in order to use the finally block. The execution of finally block is guaranteed whether there is an exception or not.

Example

import java.io.*;
class Sample{
	public static void main(String arr[]) throws IOException{
		
		FileWriter fw = new FileWriter("text.txt");
		try{
			fw.write("This is a test");
		}
		catch(IOException e){
			e.printStackTrace();
		}
		finally{
			fw.close();
			System.out.println("FileWriter is closed");
		}
	}
	
}

Note: Cleanup code should never be written in the try block because there no guarantee that all the lines in the try block will be executed.

7. throw keyword

Till now we have seen how to handle exceptions that are thrown by the JVM implicitly, but that is not limited. 'throw' keyword is used to throw an exception explicitly. It can be useful in situations where we want to throw an exception manually.

Example

class Sample{
	public static void main(String arr[]){
		
		try{
			throw new ArithmeticException();
		}
		catch(ArithmeticException e){
			System.out.println("ArithmeticException is caught");
		}
	}
	
}

Output: ArithmeticException is caught

The 'throw' keyword is mostly used to throw user-defined exceptions.

8. throw vs. throws

The 'throw' keyword is used to throw an exception manually. It is used in the method body.

The 'throws' keyword is used to declare an exception it warns the programmer that a method may throw an exception and they should provide the exception handling code accordingly. It is used in the method signature.

9. Checked and Unchecked exceptions

All the exceptions fall under one of the two categories.

  1. Checked exceptions 
  2. Unchecked exceptions

9.1. Checked exceptions

This type of exception needs to be managed by the methods or declared to be thrown before compilation otherwise, the program won't compile. These exceptions are checked by the compiler at the time of compilation that is why these are known as checked exceptions.

IOException, SQLException are examples of checked exceptions. The compiler will complain while compiling if these exceptions are not handled.

The program below will not compile

import java.io.*;
class Sample{
	public static void main(String arr[]){
	
		FileWriter fw = new FileWriter("test.txt");
		fw.write("hello");
		fw.close();
	}
}

Error:

Sample.java:5: error: unreported exception IOException; must be caught or declared to be thrown
		FileWriter fw = new FileWriter("test.txt");
		                ^
Sample.java:6: error: unreported exception IOException; must be caught or declared to be thrown
		fw.write("hello");
		        ^
Sample.java:7: error: unreported exception IOException; must be caught or declared to be thrown
		fw.close();

Exception needs to be declared thrown or handled by try-catch blocks before compilation because this program can cause a checked exception.

9.2. Unchecked exceptions

These are the exceptions about which the compiler won't complain even if they are not handled by the programmer. The compiler does not checks whether these exceptions are handled or not that is why these are called unchecked exceptions.

NullPointerExceptions, ArithmeticExceptions are examples of unchecked exceptions.

Example:

class Sample{
	public static void main(String arr[]){
		
		doSomething();	
	}
	
	public static void doSomething(){
		System.out.println(8/0);
	}
}

This program will compile fine although it will throw an 'ArithmeticException' at runtime. The 'ArithmeticExceptions' are not checked by the compiler because it falls under the unchecked exceptions.

Note: All the 'Runtime exceptions' and 'Errors' are 'Unchecked' and the rest of the exceptions are 'Checked' exceptions

10. Methods to print exception details

Java provides some methods to print the details of the exception:

10.1. printStackTrace() method

printStackTrace() prints the complete info of an exception which includes the name of the exception, description of the exception, and the complete stack trace.

Example:

class Sample{
	public static void main(String arr[]){
		try{			
			doSomething();
		}catch(ArithmeticException e){
			e.printStackTrace();
		}
	}
	
	public static void doSomething(){
		System.out.println(8/0);
	}
}

Output:

java.lang.ArithmeticException: / by zero
	at Sample.doSomething(Sample.java:12)
	at Sample.main(Sample.java:5)

The details complete details of the exception are printed with the name, description, and stack trace.

  Note: The default exception handler uses this method to print the exception details.

10.2. System.out.println() method

This method internally calls the toString() method of the 'Object' class which returns the details of the exception object as a string. This method only prints the Name and Description of the exception.

Replacing the code in the catch block from the above example

catch(ArithmeticException e)
{
	System.out.println(e);
}

Output: java.lang.ArithmeticException: / by zero

10.3. getMessage() method

This method only returns the description of the exception and nothing else.

Again replacing the code of the catch block

catch(ArithmeticException e)
{
	System.out.println(e.getMessage());
}

Output: / by zero

11. Summary

In this article, we saw what are exceptions in java, how to handle them, the use of multiple catch blocks, the use of keywords like throw, and finally and some different methods to print exception details.