September 14, 2022

Java Exception Handling Best Practices

In this post some of the exception handling best practices in Java are listed. Following these best practices in your Java code will help you in writing robust code.

Java Exception handling best practices

1. Don’t ignore exception- Exception handling especially the checked exception gives you a chance to recover from the thrown exception. Thus having a catch block that is empty defeats the very purpose of exception handling.

You need to avoid code like this-

try {
  ...
  ...
} catch( IOException e ) {

}

Even if you are pretty sure that there won’t be any exception in the block of code, still log the error at least. In the rare case exception is thrown in the block at least you will be having some log message to find out what went wrong.

try {
  ...
  ...
} catch( IOException e ) {
  logger.error(“Exception caught ” + e.getMessage());
}

2. Always clean up resources in finally block- If you are using resources like I/O streams, DB connection, socket connection in your code ensure that you close them in finally block.

Closing them with in the try block may work fine if there is no exception. If there is any exception thrown in the try block and the normal flow is disrupted, code for closing the resources may never get executed. To avoid that always close the resources in finally block as finally block is always executed whether error is thrown or not.

Java 7 onward you can also use try-with-resource statement for ensuring resources clean up. Using try-with-resource will make your code shorter too.

3. Don’t use parent class as “catch all” solution– Using parent class like Throawble, Exception or RunTimeException as a generic exception handler is not a good practice.

You should always try to throw the specific exception class that can be thrown from the code block. That makes your code more readable.

You should throw specific exception-

public void method1 throws ParseException {
 ..
 ..
}

Not generic “catch all” exception.

public void method1 throws Exception {
 ..
 ..
}

You should not catch Throwable like this.

try {
} catch(Throwable t) {
  t.printStackTrace();//Should not do this
}

Throwable is the superclass of all errors and exceptions in Java. Catching Throwable means you are catching errors too from which you can't recover from like OutOfMemoryError, StackOverFlowError. Which is against the recommended approach that application should not try and recover from Errors such as these.

4. Throw early or fail fast- One of the exception handling best practices in Java is to throw early. By throwing an exception early (also known as "failing fast"), the exception becomes both more specific and more accurate. The stack trace immediately shows what went wrong.

The exception stack trace helps pinpoint where an exception occurred by showing us the exact sequence of method calls that lead to the exception, along with the class name, method name, source code filename, and line number for each of these method calls.

Now let's see an example

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

public class ReadPreference {
  public static void main(String[] args) {
    File fileName = new File("");
    try{
      readFile(fileName);
    }catch (FileNotFoundException e){
      e.printStackTrace();
    }catch (EOFException e){
      e.printStackTrace();
    }
  }
	
  private static void readFile(File fileName) throws 
    FileNotFoundException, EOFException{
    InputStream in = new FileInputStream(fileName);        
  }
}
Output
java.io.FileNotFoundException: 
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(Unknown Source)
	at java.io.FileInputStream.<init>(Unknown Source)
	at com.knpcode.ReadPreference.readFile(ReadPreference.java:24)
	at com.knpcode.ReadPreference.main(ReadPreference.java:14)

If you scan the stack trace it looks like there is some problem with the open() method of the FileInputStream class. In the code you can see that real problem is passing space as file name. So checking that condition immediately in the method you can throw an exception early.

Changed Java program with file name check condition.

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

public class ReadPreference {
  public static void main(String[] args) {
    File fileName = new File("");
    try{
      readFile(fileName);
    }catch (FileNotFoundException e){
      e.printStackTrace();
    }catch (EOFException e){
      e.printStackTrace();
    }
  }
	
  private static void readFile(File fileName) throws FileNotFoundException, EOFException, IllegalArgumentException{
    if(fileName == null || fileName.getPath().equals("")){
      throw new IllegalArgumentException("File Name not present");
    }
    InputStream in = new FileInputStream(fileName);        
  }
}
Output
Exception in thread "main" java.lang.IllegalArgumentException: File Name not present
	at com.knpcode.ReadPreference.readFile(ReadPreference.java:25)
	at com.knpcode.ReadPreference.main(ReadPreference.java:14)
Now the exception message is more precise. 5.

Catch late- A common mistake is to catch an exception before the program can handle it in an appropriate manner. For checked exceptions, Java compiler enforces that exception either be caught or declared. The natural tendency is to immediately wrap the code in a try block and catch the exception to stop the compile time errors.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class ReadPreference {
  public static void main(String[] args) {
    File fileName = new File("");
    readFile(fileName);  
  }
	
  private static void readFile(File fileName){
    InputStream in = null;
    try {
      in = new FileInputStream(fileName);
    } catch (FileNotFoundException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }     
    try {
      in.read();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } 
  }
}
Output
java.io.FileNotFoundException: 
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(Unknown Source)
	at java.io.FileInputStream.<init>(Unknown Source)
	at com.knpcode.ReadPreference.readFile(ReadPreference.java:22)
	at com.knpcode.ReadPreference.main(ReadPreference.java:15)
Exception in thread "main" java.lang.NullPointerException
	at com.knpcode.ReadPreference.readFile(ReadPreference.java:28)
	at com.knpcode.ReadPreference.main(ReadPreference.java:15)

The above code catches FileNotFoundException, when it really cannot do anything to recover from the error. If the file is not found, the rest of the method certainly cannot read from the file.

Calling the code with the file that doesn't exist will result in FileNotFoundException getting logged and then the program tries to read data from the file. Since the file doesn't exist, in is null, and a NullPointerException gets thrown.

The way to pass responsibility for handling exceptions further up the call chain is to declare the exception in the throws clause of the method. When declaring which exceptions may be thrown, remember to be as specific as possible. With these changes the code would look like as follows.

public class ReadPreference {
  public static void main(String[] args) {
    File fileName = new File("");
    try{
      readFile(fileName);
    }catch (FileNotFoundException e){
      e.printStackTrace();
    }catch (IOException e){
      e.printStackTrace();
    }   
  }
	
  private static void readFile(File fileName) throws IllegalArgumentException, 
    FileNotFoundException, IOException{
    if(fileName == null || fileName.getPath().equals("")){
      throw new IllegalArgumentException("File Name not present");
    }                 
    InputStream in = new FileInputStream(fileName);
    in.read();  
  }
}

6. Document the thrown exceptions- Document the exceptions, which are declared in the method signature, in Javadoc. For that use @throws in your Javadoc specifying the exception and the possible cause when that exception can be thrown.

	
/**
* 
* @param fileName
* @throws IllegalArgumentException --if filename is not passed
* @throws FileNotFoundException - if passed file doesn't exist
* @throws IOException - For other I/O errors
*/
private static void readFile(File fileName) throws IllegalArgumentException, 
   FileNotFoundException, IOException{
  ...
  ...
}

7. Don’t use exception for flow control- When an exception is thrown whole process creating an exception object, going through the method stack to look for the exception handler that can handle the thrown exception is followed. So try to use this exception handling mechanism for exceptional conditions only. Using exception handling as flow control tool means slowing the performance of your application for simple things that can easily be checked with conditional statements. Use if condition for checking-

int i = 7;
int value;
int[] numArr = {4,5,6};
if(i < numArr.length){
  value = numArr[i];
}
Rather than this-
int i = 7;
int value;
int[] numArr = {4,5,6};
try{
  value = numArr[i];
}catch (ArrayIndexOutOfBoundsException ex) {
  ex.printStackTrace();
}

8. Don’t Log and throw– Doing both logging as well as rethrowing an exception is an anti-pattern and not a good practice. It adds multiple error messages in your log for the same exception.

try{
  value = numArr[i];
}catch (ArrayIndexOutOfBoundsException ex) {
  logger.info("exception caught " + ex);
  throw ex;
}
Output
INFO: exception caught java.lang.ArrayIndexOutOfBoundsException: 7
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 7
	at com.knpcode.ReadPreference.main(ReadPreference.java:18)

As you can see in the output you have multiple error messages for the same exception.

9. Java exception handling slows the overall performance- When an exception is thrown in your code an exception object is created and the method stack is searched for the appropriate exception handler. This slows the overall performance of your application so use exception handling mechanism for exceptional conditions. Where you can avoid exception handling by doing some conditional checking do that rather than using try-catch block. Some of this is already discussed in point 7 “Don’t use exception for flow control”.

10. Include the original exception- If you are throwing another exception after catching the original exception, as per the exception handling best practices in Java you should ensure that original exception is not lost. Use the constructor with the cause parameter to retain the original exception.

catch (IllegalArgumentException e) {
   throw new MyException ("Exception caught: ", e);  
}

11. Convert layer specific exception- If an exception is thrown in any layer of your application which is specific to that layer ensure that you wrap it in different exception. This practice helps with loose coupling where implementation of any specific layer is kept abstracted from another layer.

As example- In DAOLayer you may have to catch SQLException but that should not be propagated to another layer. You can wrap it into another exception and throw it.

catch(SQLException ex){
  throw new MyException("DB error", ex);
}

That's all for the topic Java Exception Handling Best Practices. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment