What are the best practices followed in exception handling in Java?

An Exception is an event that causes your normal Java application flow to be disrupted. Exceptions can be caused by using a reference that is not yet initialized, dividing by zero, going beyond the length of an array, or even JVM not being able to assign objects on the heap.

In Java, an exception is an Object that wraps the error that caused it and includes information like:

  • The type of error that caused the exception with the information about it.
  • The stack trace of the method calls.
  • Additional custom information that is related to the error.

Various Java libraries throw exceptions when they hit a state of execution that shouldn’t happen – from the standard Java SDK to the enormous amounts of open source code that is available as a third-party library.

Exceptions can be created automatically by the JVM that is running our code or explicitly our code itself. We can extend the classes that are responsible for exceptions and create our own, specific Java exceptions that handle unexpected situations. But keep in mind that throwing an exception doesn’t come for free. It is expensive. The larger the call stack the more expensive the exception.

2. What Is the Purpose of the Throw and Throws Keywords?

The throws keyword is used to specify that a method may raise an exception during its execution. It enforces explicit exception handling when calling a method:

public void simpleMethod() throws Exception { // ... }

The throw keyword allows us to throw an exception object to interrupt the normal flow of the program. This is most commonly used when a program fails to satisfy a given condition:

if (task.isTooComplicated()) { throw new TooComplicatedException("The task is too complicated"); }

3. How Can You Handle an Exception?

By using a try-catch-finally statement:

try { // ... } catch (ExceptionType1 ex) { // ... } catch (ExceptionType2 ex) { // ... } finally { // ... }

The block of code in which an exception may occur is enclosed in a try block. This block is also called “protected” or “guarded” code.

If an exception occurs, the catch block that matches the exception being thrown is executed, if not, all catch blocks are ignored.

The finally block is always executed after the try block exits, whether an exception was thrown or not inside it.

4. What Is the Difference Between a Checked and an Unchecked Exception?

A checked exception must be handled within a try-catch block or declared in a throws clause; whereas an unchecked exception is not required to be handled nor declared.

Checked and unchecked exceptions are also known as compile-time and runtime exceptions respectively.

All exceptions are checked exceptions, except those indicated by Error, RuntimeException, and their subclasses.

5. What Is the Difference Between an Exception and Error?

An exception is an event that represents a condition from which is possible to recover, whereas error represents an external situation usually impossible to recover from.

All errors thrown by the JVM are instances of Error or one of its subclasses, the more common ones include but are not limited to:

  • OutOfMemoryError – thrown when the JVM cannot allocate more objects because it is out memory, and the garbage collector was unable to make more available
  • StackOverflowError – occurs when the stack space for a thread has run out, typically because an application recurses too deeply
  • ExceptionInInitializerError – signals that an unexpected exception occurred during the evaluation of a static initializer
  • NoClassDefFoundError – is thrown when the classloader tries to load the definition of a class and couldn't find it, usually because the required class files were not found in the classpath
  • UnsupportedClassVersionError – occurs when the JVM attempts to read a class file and determines that the version in the file is not supported, normally because the file was generated with a newer version of Java

Although an error can be handled with a try statement, this is not a recommended practice since there is no guarantee that the program will be able to do anything reliably after the error was thrown.

6. What Is a Stack trace and How Does It Relate to an Exception?

A stack trace provides the names of the classes and methods that were called, from the start of the application to the point an exception occurred.

It's a very useful debugging tool since it enables us to determine exactly where the exception was thrown in the application and the original causes that led to it.

7. Exception Class Hierarchy

Java is an object-oriented language, and thus, every class will extend the java.lang.Object. The same goes for the Throwable class, which is the base class for the errors and exceptions in Java.

8. The Try-With-Resources Block

The last thing when it comes to handling exceptions in Java is the try-with-resources block. The idea behind that Java language structure is opening resources in the try section that will be automatically closed at the end of the statement. That means that instead of including the finally block we can just open the resources that we need in the try section and rely on the Java Virtual Machine to deal with the closing of the resource.

For the class to be usable in the try-with-resource block it needs to implement the java.lang.AutoCloseable, which includes every class implementing the java.io.Closeable interface. Keep that in mind.

An example method that reads a file using the FileReader class which uses the try-with-resources might look as follows:

public void readFile(String filePath) { try (FileReader reader = new FileReader(filePath)) { // do something } catch (FileNotFoundException ex) { // do something when file is not found } catch (IOException ex) { // do something when issues during reader close happens } }

Of course, we are not limited to a single resource and we can have multiple of them, for example:

public void readFiles(String filePathOne, String filePathTwo) { try ( FileReader readerOne = new FileReader(filePathOne); FileReader readerTwo = new FileReader(filePathTwo); ) { // do something } catch (FileNotFoundException ex) { // do something when file is not found } catch (IOException ex) { // do something when issues during reader close happens } }

One thing that you have to remember is that you need to process the Java exceptions that happen during resources closing in the catch section of the try-catch-finally block. That’s why in the above examples, we’ve included the IOException in addition to the FileNotFoundException. The IOException may be thrown during FileReader closing and we need to process it.

9. Catching User-Defined Exceptions

The Throwable and Exception are Java classes and so you can easily extend them and create your own exceptions. Depending on if you need a checked or unchecked exception you can either extend the Exception or the RuntimeException class. To give you a very simple example on how such code might look like, have a look at the following fragment:

public class OwnExceptionExample { public void doSomething() throws MyOwnException { // code with very important logic throw new MyOwnException("My Own Exception!"); } class MyOwnException extends Exception { public MyOwnException(String message) { super(message); } } }

In the above trivial example we have a new Java Exception implementation called MyOwnException with a constructor that takes a single argument – a message. It is an internal class, but usually you would just move it to an appropriate package and start using it in your application code.


In this guide, I would like to explain Java Exception Handling Best Practices. We can follow these best practices in the day to day project work. This post belongs to Java Best Practices Series category.
Trust me exception handling in Java isn’t an easy topic. Beginners find it hard to understand and even experienced developers can spend hours discussing how and which Java exceptions should be thrown or handled.

What are the best practices followed in exception handling in Java?

Let's discuss each of the Exception Handling Best Practices with examples.


A common mistake occurs when closing the resources. The most common mistake is to close the resource at the end of the try block. 
For Example:

public void doNotCloseResourceInTry() { FileInputStream inputStream = null; try { File file = new File("./tmp.txt"); inputStream = new FileInputStream(file); // use the inputStream to read a file // do NOT do this inputStream.close(); } catch (FileNotFoundException e) { LOGGER.error(e.getMessage()); } catch (IOException e) { LOGGER.error(e.getMessage()); } }

The problem is that this approach seems to work perfectly fine as long as no exception gets thrown. All statements within the try block will get executed, and the resource gets closed. The problem is when an exception occurs within a try block and you might not reach the end of the try block. And as a result, you will not close the resources. You should, therefore, put all your clean up code into the finally block or use a try-with-resources statement.

Let's write some simple examples to demonstrate this:

public void closeResourceInFinally() { FileInputStream inputStream = null; try { File file = new File("./tmp.txt"); inputStream = new FileInputStream(file); // use the inputStream to read a file } catch (FileNotFoundException e) { LOGGER.error(e.getMessage()); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { LOGGER.error(e.getMessage()); } } } }

Java 7’s Try-With-Resource Statement

try(// open resources here){ // use resources } catch (FileNotFoundException e) { // exception handling } // resources are closed as soon as try-catch block is executed.

public void automaticallyCloseResource() { // Example 1 File file = new File("./tmp.txt"); try (FileInputStream inputStream = new FileInputStream(file);) { // use the inputStream to read a file } catch (FileNotFoundException e) { LOGGER.error(e.getMessage()); } catch (IOException e) { LOGGER.error(e.getMessage()); } // Example 2 try (BufferedReader br = new BufferedReader(new FileReader( "C:\\ramesh.txt"))) { System.out.println(br.readLine()); } catch (IOException e) { e.printStackTrace(); } }

try with resources benefits:

  • More readable code and easy to write.
  • Automatic resource management.
  • The number of lines of code is reduced.
  • No need to finally block just to close the resources.
  • We can open multiple resources in try-with-resources statement separated by a semicolon. For example, we can write the following code:

try (BufferedReader br = new BufferedReader(new FileReader( "C:\\ramesh.txt")); java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(FileSystems.getDefault(). getPath("C:\\journaldev.txt"), Charset.defaultCharset())) { System.out.println(br.readLine()); } catch (IOException e) { e.printStackTrace(); }

Always prefer to throw a specific exception and don't through the generic exception. 

public void someMethod(){ // Better public void someMethod() throws SpecificException1, SpecificException2 { ... } // Avoid public void someMethod1() throws Exception { .. } }

public void doNotDoThis() throws Exception { ... } public void doThis() throws NumberFormatException { ... }

Example:
// Avoid package com.igate.primitive; public class PrimitiveType { public void downCastPrimitiveType() { try { System.out.println(" i [" + i + "]"); } catch(Exception e) { e.printStackTrace(); } catch(RuntimeException e) { e.printStackTrace(); } catch(NullPointerException e) { e.printStackTrace(); } } } // Better public void someMethod(){ try{ throw new SpecificException1(); // code here }catch(SpecificException1 exception){ }catch (Exception e) { } }

Well, its one step more serious trouble. Because java errors are also subclasses of the Throwable. Errors are irreversible conditions that cannot be handled by JVM itself. And for some JVM implementations, JVM might not actually even invoke your catch clause on an Error.

public void doNotCatchThrowable() { try { // do something } catch (Throwable t) { // don't do this! } }

catch (NoSuchMethodException e) { // in correct way throw new MyServiceException("Some information: " + e.getMessage()); }

This destroys the stack trace of the original exception and is always wrong. The correct way of doing this is:

catch (NoSuchMethodException e) { throw new MyServiceException("Some information: " , e); //Correct way }

Most IDEs help you with this best practice. They report an unreachable code block when you try to catch the less specific exception first.

You can see an example of such a try-catch statement in the following code snippet. The first catch block handles all NumberFormatExceptions and the second one all IllegalArgumentExceptions which are not a NumberFormatException.

public void catchMostSpecificExceptionFirst() { try { doSomething("A message"); } catch (NumberFormatException e) { log.error(e); } catch (IllegalArgumentException e) { log.error(e) } }

Don't ignore the exceptions. 

Example:

// avoid public void doNotIgnoreExceptions() { try { // do something } catch (NumberFormatException e) { // this will never happen } }

Log the exceptions Example:

public void logAnException() { try { // do something } catch (NumberFormatException e) { log.error("This should never happen: " + e); } }

try { someMethod(); //Throws exceptionOne } finally { cleanUp(); //If finally also threw any exception the exceptionOne will be lost forever }

This is fine, as long as cleanUp() can never throw an exception. In the above example, if someMethod() throws an exception, and in the finally block also, cleanUp() throws an exception, that second exception will come out of the method and the original first exception (correct reason) will be lost forever. If the code that you call in a finally block can possibly throw an exception, make sure that you either handle it or log it. Never let it come out of the finally block.

Never leave printStackTrace() after finishing your code. Chances are one of your fellow colleagues will get one of those stack traces eventually, and have exactly zero knowledge as to what to do with it because it will not have any contextual information appended to it.

try { someMethod(); //Method 2 } finally { cleanUp(); //do cleanup here }

This is also a good practice. If inside your method you are accessing some method 2, and method 2 throw some exception which you do not want to handle in method 1 but still want some cleanup in case exception to occur, then do this cleanup in finally block. Do not use catch block.

Always validate user input in a very early stage, even before it reached to the actual controller. It will help you to minimize the exception handling code in your core application logic. It also helps you in making application consistent if there is some error in user input. 
Example:

if(user.getEmail == null){ throw new BadRequestException(); } if(user.getAddress== null){ throw new BadRequestException(); } if(user == null){ throw new UserNotFoundException(); }

If you specify the short message in the exception, example:

try { new Long("xyz"); } catch (NumberFormatException e) { e.printStackTrace(); }

The above code will print :

java.lang.NumberFormatException: For input string: "xyz" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:589) at java.lang.Long.<init>(Long.java:965)

So always pass descriptive messages to the exceptions.

Watch this course on YouTube at Spring Boot Tutorial | Fee 10 Hours Full Course