Guide to Java Exception Handling Best Practices


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.
Let's discuss each of the Exception Handling Best Practices with examples.


1. Clean up Resources in a Finally Block or Use a Try-With-Resource Statement

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.
You can use either finally block or Java 7’s Try-With-Resource Statement to close the resources. 
Let's write some simple examples to demonstrate this:
Use finally block
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
Syntax:
try(// open resources here){
    // use resources
} catch (FileNotFoundException e) {
 // exception handling
}
// resources are closed as soon as try-catch block is executed.
Example:
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();
       }
}
  • 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();
}

2. Throw Specific Exception

Always prefer to throw a specific exception and don't through the generic exception. 
Example 1:
public void someMethod(){
    // Better
    public void someMethod() throws SpecificException1, SpecificException2 { ... }
    
    // Avoid
    public void someMethod1() throws Exception { .. }
}
Example 2:
public void doNotDoThis() throws Exception { ... }
 
public void doThis() throws NumberFormatException { ... }

3. Do not catch the Exception class rather catch specific subclasses

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) {
  
    }
}

4. Never catch a Throwable class

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.
Example:
public void doNotCatchThrowable() {
    try {
         // do something
    } catch (Throwable t) {
         // don't do this!
    }
}

5. Always correctly wrap the exceptions in custom exceptions so that stack trace is not lost

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
}

6. Catch the most specific exception first

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)
      }
}

7. Don’t ignore exceptions rather log the exceptions

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);
    }
}

8. Never throw any exception from finally block

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.

9. Don’t use printStackTrace() statement or similar methods

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.

10. Use finally blocks instead of catch blocks if you are not going to handle the exception

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.

11. Validate user input to catch adverse conditions very early in request processing

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(); 
}

12. Throw Exceptions With Descriptive Messages

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.

Comments