Exception Handling in Java

Let's say you were going for an interview; you are thoroughly prepared and confident that you will land this job. So you get in your car at the right time and drive to the company. You avoid the road that is usually congested and opt for a narrower but faster lane that day. But to your dismay, you find a big truck blocking that road! You see, exceptions are inevitable.

Similarly, Exceptions in Java are neither uncommon. Exceptions refers to all those unforeseen events that occur during the runtime of a program. If they are not identified and handled during the compiler stage, working on them later becomes a tedious task.

In this article, we will discuss the two broad classes of exceptions - the Checked and Unchecked and all that comes within them. Then we will learn how to handle these exceptions using the tried and tested methods, including try-catch-finally and try-with-resources.

Table Of Contents

What is Java Exception?

An exception is any abnormal condition that disrupts the natural workflow of a program. It is an object thrown at runtime. We name this object as the exception object. It contains all the information about the Exception - like its name, description and the state of the running programming when this particular Exception occurred.

Exceptions may arise due to various reasons - it may be due to hardware outage, insufficient memory, network or server connection failure or a simple typing error made by the user.

The entire program is compiled (to convert programming code into a runnable program) before running it. Changes in the core program would mean revisiting the entire code once again, and it is a challenging task. So, when any error occurs after the compile time, it becomes very difficult to fix it.

While Exception is a condition that can be handled, errors constitute serious problems beyond the programmer's control.

How to handle Java Exception?

Java has a brilliant and object-oriented approach for tackling these exceptions - by using special exception handlers.

Let's say we have made ten statements in a java program; unfortunately, an exception occurs in the fifth statement. Now exceptions are errors and thus, the program stops giving any output for an error. This causes all of our next statements, i.e., statements 6 - 10 to remain unread. Thus our entire program terminates after statement 4.

statement(1);
statement(2);
statement(3);
statement(4);
statement(5); //exception occurs
statement(6);
statement(7);
statement(8);
statement(9);
statement(10);

This is when exception handling comes to the rescue! There are two different ways to handle an exception in a java program: the try-catch-finally method or the throw-throws keyword. We will look into these methods later in the blog.

Java Exception Class Hierarchy

Throwable class is the main root class in a java exception hierarchy. It then branches into two subclasses - Exception and errors.

Exception class includes conditions that can be handled explicitly. For example, IOException, SQLException, ClassNotFoundException and RuntimeException belong to this class.

The other subclass under throwable is the error subclass; these are usually errors in the runtime environment itself and, thus are not handled by the JVM. StackOverflowError is an example of this kind.

Java Exception Types

Types of Java Exceptions

In Java, exceptions are broadly classified into two categories:

  1. Checked
  2. Unchecked

They both come under built-in exceptions in the java class, but java also provides a user-defined exception optionally.

1. Checked Exceptions

Checked exception is an exception that must be declared in a method or constructor's throws clause if it can be thrown by the method or constructor's implementation. This means that the compiler will check for the presence of a throws clause and will generate an error if the exception is not declared.

Checked exceptions are typically used for exceptions that are expected to occur and can be handled by the calling code. For example, if a method attempts to open a file, it may throw a FileNotFoundException if the file does not exist. In this case, the calling code can handle the exception by catching it and taking appropriate action, such as displaying an error message or prompting the user for a different file.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class CheckedExceptionExample {
    public static void main(String[] args) {
        // Read file specified by first argument
        String filePath = args[0];
        String fileContent = readFile(filePath);

        // Print file content
        System.out.println(fileContent);
    }

    public static String readFile(String filePath) throws IOException {
        // Open file
        Path path = Paths.get(filePath);
        byte[] data = Files.readAllBytes(path);

        // Convert data to string
        return new String(data);
    }
}

In this example, the readFile() method attempts to open and read the file specified by the filePath argument. If the file does not exist, or if any other input/output error occurs, the method will throw an IOException.

Since the readFile() method may throw an IOException, it must declare this exception in its throws clause. The main() method, which calls the readFile() method, must either handle the exception by catching it in a try-catch block, or declare it in its own throws clause if it also cannot handle the exception.

For example, the main() method could handle the IOException as follows:

public static void main(String[] args) {
    // Read file specified by first argument
    String filePath = args[0];
    String fileContent = "";
    try {
        fileContent = readFile(filePath);
    } catch (IOException ex) {
        // Handle IOException
        System.err.println("Error reading file: " + ex.getMessage());
    }

    // Print file content
    System.out.println(fileContent);
}

In this case, the main() method catches the IOException thrown by the readFile() method, and prints an error message to the standard error stream. This allows the program to continue execution, even if the file cannot be read.

2. Unchecked Exceptions

In Java, an unchecked exception is an exception that does not need to be declared in a method or constructor throws clause. This means that the compiler does not check for the presence of a throws clause and will not generate an error if the exception is not declared.

Unchecked exceptions are typically used for exceptions that are not expected to occur and cannot be easily handled by the calling code. For example, if a method attempts to access an array element at an index that is out of bounds, it may throw an ArrayIndexOutOfBoundsException. In this case, the calling code cannot do much to prevent or handle the exception, as it is a programming error that must be fixed by the developer.

An example of an unchecked exception in Java is the NullPointerException. This exception is thrown when an application attempts to use a null object reference. To handle this exception, the calling code must either catch it in a try-catch block, or allow it to propagate up the call stack. For example:

public void processData(Data data) {
    // Code that may throw a NullPointerException
    data.doSomething();
}

In this example, the processData() method may throw a NullPointerException if the data argument is null. Since this exception is an unchecked exception, the method does not need to declare it in the throws clause. The calling code must either catch the exception in a try-catch block, or allow it to propagate up the call stack.

Other examples of unchecked exceptions in Java include ArithmeticException, which is thrown when an arithmetic operation is attempted on a null value, and IllegalArgumentException, which is thrown when a method is passed an illegal or inappropriate argument.

Here is an example code that demonstrates the use of an unchecked exception in Java:

public class UncheckedExceptionExample {
    public static void main(String[] args) {
        // Read numbers from arguments
        int[] numbers = new int[args.length];
        for (int i = 0; i < args.length; i++) {
            numbers[i] = Integer.parseInt(args[i]);
        }
       
        // Calculate sum of numbers
        int sum = 0;
        for (int number : numbers) {
            sum += number;
        }
 
        // Print sum
        System.out.println("Sum: " + sum);
    }
}

In this example, the main() method attempts to read a list of numbers from the command-line arguments and calculate their sum. If the arguments are not valid numbers, the Integer.parseInt() method will throw a NumberFormatException.

Since the NumberFormatException is an unchecked exception, the main() method does not need to declare it in the throws clause. Instead, the exception will be propagated up the call stack, and the program will terminate with an error message.

To handle the NumberFormatException, the calling code (in this case, the JVM) must catch the exception and take appropriate action. For example, the JVM could catch the exception and print a usage message, as follows:

public class UncheckedExceptionExample {
    public static void main(String[] args) {
        try {
            // Read numbers from arguments
            int[] numbers = new int[args.length];
            for (int i = 0; i < args.length; i++) {
                numbers[i] = Integer.parseInt(args[i]);
            }
 
            // Calculate sum of numbers
            int sum = 0;
            for (int number : numbers) {
                sum += number;
            }
 
            // Print sum
            System.out.println("Sum: " + sum);
        } catch (NumberFormatException ex) {
            // Handle NumberFormatException
            System.err.println("Usage: java UncheckedExceptionExample <number1> <number2> ... <numberN>");
        }
    }
}

In this case, the main() method catches the NumberFormatException and prints a usage message to the standard error stream. This allows the program to continue execution, even if the arguments are not valid numbers.

Common Java Exception Keywords

Keyword Description
try Try block is where we keep the protected code. It cannot be used independently, and is always followed either by a catch block or a finally block.
catch Catch block handles exceptions. This catch block catches all the exceptions thrown while running try block. A finally block may follow it.
finally Finally block acts as a clean-up statement. It is embedded irrespective of an exception occurrence.
throw "throw" keyword is used to throw an exception.
throws Throws keyword does'nt throw an exception, it rather postpones the handling of a checked exception. Therefore, it can be considered as a method declaration.

Java Exception Handling

We have reached to the crucial part of this entire article, exceptions occur every time. We can't stop them, but we can manage them so that they don't affect our program.

Java Exception Handling
Java Exception Handling

We will discuss three ways in which we can handle java exceptions here,

1. Try Catch Block

The try and catch keywords are used to catch exceptions in a method. Exception-prone code is enclosed in a try or catch block. Code within a try or catch block is referred to as protected code. The syntax for a try-catch block is -

try {
   // Protected code
} catch (ExceptionName e1) {
   // Catch block
}

As you can see, we have placed the code to generate an exception inside the try block. A catch block follows every try block. Whenever try black confronts an exception, it is caught by the catch block.

Catch block is dependent on try block and it cannot function without it.

Example of a try-catch block:

class Main {
    public static void main(String[] args) {

        try {
            // code that generate Exception
            int divideByZero = 5 / 0;
            System.out.println("Rest of code in try block");
        } catch (ArithmeticException e) {
            System.out.println("ArithmeticException => " + e.getMessage());
        }
    }
}

Output

ArithmeticException => / by zero

In this example, we are trying to divide an integer by zero. As you know, this is not logically possible and hence creates an exception. Rest of the code inside the try block is therefore skipped. The catch block catches this exception and executes the statement given inside it. If there is no exception thrown within the try block, the catch part gets skipped and the program produces a regular output.

In both the try and catch blocks, you can include multiple statements within one try or catch block or you can also put each statement under separate try blocks.

Each catch block is an exception handler which handles the Exception to the type indicated by its argument. This argument must include the name of the class that it inherits from the throwable class.

Multiple catch blocks

You may include multiple catch blocks after a try block, to ensure efficient handling of any exception that may occur. We can assign exception types in each of these catch blocks. If an exception occurs in the protected code (placed within the try block), and the data type of the thrown Exception matches the with that specified in catch block 1, it gets caught there. If not, it gets passed onto the next catch block and so on. This way the multiple catch block works until the entire program is tried and caught for exceptions.

A sample of how multiple catch block works is given below:

try {
    file = new FileInputStream(fileName);
    x = (byte) file.read();
} catch (IOException i) {
    i.printStackTrace();
    return -1;
} catch (FileNotFoundException f) // Not valid! {
f.printStackTrace();
return -1;
}

Since Java 7, you can handle more than one Exception using a single catch block, and this feature simplifies the code. The code for the same is -

catch (IOException|FileNotFoundException ex) {
   logger.log(ex);
   throw ex;

2. Throw and Throws Keywords

Throw keyword

Throw keyword is used to throw a single exception explicitly. When an exception occurs, the code moves from try block to catch block.

Example:

class Main {
    public static void divideByZero() {

        //Throw an exception
        throw new ArithmeticException("Trying to divide by 0");
    }

    public static void main(String[] args) {
        divideByZero();
    }
}

Output:

Exception in thread "main" java.lang.ArithmeticException: Trying to divide by 0
        at Main.divideByZero(Main.java:5)
        at Main.main(Main.java:9)

Throws keyword

Throws keyword in java signifies a method declaration. It declares the exceptions that might occur within the method class. When the callee does not wish to handle the Exception, the throws keyword is used to pass responsibility for handling the Exception to the caller. This is particularly useful when dealing with checked exceptions as the compiler will not allow the code to compile unless these are handled.

Example

import java.io.*;

class Main {
    // declareing the type of Exception
    public static void findFile() throws IOException {

        // code that may generate IOException
        File newFile = new File("test.txt");
        FileInputStream stream = new FileInputStream(newFile);
    }

    public static void main(String[] args) {
        try {
            findFile();
        } catch (IOException e) {
            System.out.println(e);
        }
    }
}

Output

java.io.FileNotFoundException: test.txt (The system cannot find the file specified)

When we run this program, if the file test.txt does not exist, FileInputStream throws a FileNotFoundException which extends the IOException class.

While Throws are used to postpone handling a checked exception , Throw is used to invoke an exception explicitly.

3. Finally Block

Finally block is an eternal option. It always executes whether  an exception is thrown or not.

Finally block follows the try and catch blocks. There can be zero or more catch blocks for each try block, but only one final block. It is usually used for clean-up type of statements.

Syntax for executing a Finally Block -

try {
    //code
} catch (ExceptionType1 e1) {
    // catch block
} finally {
    // finally block always executes
}

An example of Java exception handling using finally block -

class Main {
    public static void main(String[] args) {
        try {
            // code that generates Exception
            int divideByZero = 5 / 0;
        } catch (ArithmeticException e) {
            System.out.println("ArithmeticException => " + e.getMessage());
        } finally {
            System.out.println("This is the finally block");
        }
    }
}

Output

ArithmeticException => / by zero

Finally block includes important clean up codes like codes that might have been accidentally skipped by return, continue or break and for closing a file or connection. So, incorporating finally block is a good choice to prevent any leaks.

Try-Catch-Finally Block Working

4. Try-with-resources

Usually, finally block is responsible for closing all the resources and end the program properly. But with the advent of new Java 7, try-with-resources feature was added which takes up the job of the finally block and in fact is more useful since it automatically closes all the resources used within a try-catch block when ending a program.

try (FileReader fr = new FileReader("myfile.txt")) {
    // read from the file here
} catch (IOException e) {
    // handle the IOException here
}

Using this statement, you need to declare the resources within the parenthesis. Then, once the block ends, the newly created resources will automatically close themselves.

Here is an example of using the try-with-resources statement to read from a file:

try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    // Handle exception
}

In the above example, the BufferedReader is declared and instantiated within the try statement, and it is automatically closed when the try block is exited, regardless of whether an exception is thrown. This is more efficient and less error-prone than manually closing the BufferedReader in a finally block.

Examples of Java Exceptions

Exceptions can be various types. We  have listed some of the common exceptions that may occur when coding a program in java.

Given below are some scenarios where an unchecked exception can occur:

  1. ArithmeticException - Dividing any number by zero results in an  ArithmeticException.
  2. IOException -  IOException is thrown when the input-output operation fails or is interrupted.
  3. NullPointerException - In case we have assigned null value to any of the string variables, performing any operation on them would return  a NullPointerException.
  4. NumberFormatException - If you convert a string variable coded in characters to digits, it will throw a NumberFormatException.
  5. ArrayIndexOutOfBoundException - When an array exceeds its specified length, you would be given an ArrayIndexOutOfBoundException .
  6. ClassNotFoundException -  If the class definition is not found then the ClassNotFoundException is raised.
  7. FileNotFoundException -  When the file requested for does not exist in the folder or does not open, then FileNotFoundException is raised.
  8. NoSuchFieldException -  If a class does not contain a specified field or variable, then it throws NoSuchFieldException.
  9. InputMismatchException - InputMismatchException is thrown when the input doesn't match the specified pattern. For example, searching for string in a float method will produce InputMismatchException.
  10. StringIndexOutOfBoundsException - It indicates that the index requested for is beyond the size of the String object or is negative.
  11. EOFException -  Which is an abbreviated form of end of the file exception, is a part of the java.io package and is thrown when the end of file is reached when the file is being read.
  12. InterruptedException -  When a thread is in waiting or sleeping mode, unprocessed, then InterruptedException is thrown to awake it.
  13. NoSuchElementException - NoSuchElementException is thrown when the next element accessed does not exist.
  14. ConcurrentModificationException - Collection classes typically throw exceptions such as this. This Exception is thrown when the objects try to modify a resource concurrently. for example, a thread cannot modify a collection when another thread is accessing it. This is because adding two threads to the collection will result in inconsistencies since they will access the collection concurrently.

There are several more exceptions that you may come upon while dealing with a java program. However, it is only possible to include some of them here; thus, we have resorted to only 14 most common exceptions.

Custom exceptions in java

Apart from built-in exceptions, java also features customized exceptions. They are also called user-defined exceptions. These are really handy when we deal with certain exceptions that occur due to the nature of our program. They might be necessary for our business logic and workflow and hence unavoidable.

Here is an example of a custom InvalidValueException class in Java that is thrown when an invalid value is provided to a method:

public class InvalidValueException extends Exception {
  // Constructor that accepts a message
  public InvalidValueException(String message) {
    super(message);
  }
}

This custom exception class extends the Exception class and provides a constructor that accepts a message. This exception can be used in a Java program like this:

public class Main {
  public static void main(String[] args) {
    try {
      int value = -5;
      if (value < 0) {
        // throw the custom exception
        throw new InvalidValueException("Value must be positive");
      }
      // do something with the value
    } catch (InvalidValueException ex) {
      System.out.println(ex.getMessage());
    }
  }
}

In this example, the InvalidValueException is thrown when the value is negative, indicating that it is an invalid value. The InvalidValueException is caught in the catch block, where the error message is printed to the console. The output of this program would be:

Value must be positive

Custom exceptions like this InvalidValueException can be useful for providing more detailed and meaningful error messages to the user, or for handling specific error scenarios in a program.

Java Exception Handling Best Practices

i.) Use specific exceptions- The exception hierarchy is bundled with so many exception classes and subclasses. So, throwing and catching specific exception classes will make it easier for the caller to know the main cause of Exception and subsequently process them much faster.

ii.) Throw Exception early - Suppose an exception occurred at some point of the program, while debugging we will have to look at the entire stack to identify the location where the Exception occurred. But if we change our logic to check for exceptions early on, it would make the whole process much simpler.

Consider this example -

private static void processFile(String file) throws MyException {
	if (file == null) throw new MyException("File name can't be null", "NULL_FILE_NAME");

	// ... further processing
}

Output

com.journaldev.exceptions.MyException: File name can't be null
	at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37)
	at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)

iii.) Using single catch for multiple exceptions - Java 7 has this feature of sorting out all exceptions within a single catch block. Not only does it reduce our code size but also makes it more visible and manageable.

iv.) Use Finally block- Since exception handling halts the processing of a program, it is advised to include a finally block after try-catch exception handlers to close a program properly/in a proper manner. You can also use java try-with-resources block for the same.

v.) Using custom exceptions - As we already know, there are tons of exception classes out there. So creating a utility method to process different exceptions under varying data types can make exception handling swift. Also, packaging them after creating makes them more efficient.

vi.) Documenting exceptions - Using Javadoc @throws, we can specify the exceptions thrown by the method. it works well when we use this interface for other applications to work.

Conclusion

While exceptions are recurring when compiling a program, handling them becomes the most pertinent problem. Thanks to today's technology and advanced skill-sets of developers, we can handle these exceptions much more efficiently.

As you've read in the article, Java offers two kinds of exceptions - the checked and unchecked ones. It is imperative that you handle check exceptions quickly to sort out the mess in the program while handling unchecked exceptions are uncalled for! Of course, you are not required to handle them, but if you want, you can do it the same way you would for an unchecked exception. i.e., using either try-catch-finally or try-with-resources method.

Also, you can always specify the exceptions rather than handling them. In this case, the Exception becomes a part of the method definition it has to be specified or handled by all methods that call it.

This brings us to the end of our Java Exception handling article. I hope you have learned all about exceptions and how to handle them the right way.


Monitor your Java application for performance & exceptions with Atatus

With Atatus Java performance monitoring, you can monitor the performance and availability of your Java application in real-time and receive alerts when issues arise. This allows you to quickly identify and troubleshoot problems, ensuring that your application is always running smoothly.

One of the key features of Atatus is its ability to monitor the performance of your Java application down to the individual request level. This allows you to see exactly how long each request is taking to process, as well as any errors or exceptions that may have occurred. You can also see a breakdown of the different components of your application, such as the web server, database, and external services, to see how they are affecting performance.

Java performance monitoring
Java performance monitoring

In addition to monitoring performance, Atatus also allows you to track the availability of your Java application. This means that you can see when your application is down or experiencing errors, and receive alerts when this occurs. You can also see how long your application has been running, as well as any uptime or downtime trends over time.

Atatus also offers a range of tools and integrations that can help you to get the most out of your monitoring. For example, you can integrate Atatus with popular tools like Slack, PagerDuty, and Datadog to receive alerts and notifications in your preferred channels. You can also use Atatus's APIs and SDKs to customize your monitoring and build custom integrations with your own tools and systems.

Overall, Atatus is a powerful solution for monitoring and managing the performance and availability of your Java application. By using Atatus, you can ensure that your application is always running smoothly and that any issues are quickly identified and resolved.

If you are not yet a Atatus customer, you can sign up for a 14-day free trial .

Aiswarya S

Aiswarya S

Writes technical articles at Atatus.

Monitor your entire software stack

Gain end-to-end visibility of every business transaction and see how each layer of your software stack affects your customer experience.