Journal for Developers

Best Practices For Java Exception Handling

team discussions
0 170

Get 30 minute iPhone repair with Puls! Schedule your repair now!

Java Exception handling is one of the most sought out topic. If you are beginner then you might find it a little hard to understand. Even the experienced developers spend hours discussing how to handle the exceptions or in particular which type of exceptions should be thrown or handled.

Most of the development teams define their own set of rules on how to use exception handling, which are termed as the best practices.

In this journal, we are going to talk about some of the best or lets say important practices that one must follow to improve the exception handling scenarios.

#1 – Use of Finally Block for Resources clean up.

Are you using any resources in your try block, like an InputStream, which needs to close afterward. It happens quite often in situations like these we tend to close the resource at the end of the try block.

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) {
        log.error(e);
    } catch (IOException e) {
        log.error(e);
    }
}

This approach seems to work fine till the time no exception gets thrown before the closing of resource and all the statements within the try block gets executed. What happens if you call one or more methods which might throw an exception, or maybe you throw the exception yourself. This would mean that you might not reach the end of the try block. And as a result, you will not be able to close the resources.

Therefore, it’s always advisable to put all your clean up code in the finally block.

Use a Finally Block

The finally block is the block which will always be executed no matter whether there’s an exception in your try block or not. In other words we can say that, finally block executions will always happens either after the successful execution of the try block or after you handled an exception in a catch block. Hence, you can be rest assured that all the resources are cleaned up once we have used them.

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) {
        log.error(e);
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error(e);
            }
        }
    }
}

 

#2 – Java 7’s Try-With-Resource Statement

If the resource you are using implements the AutoCloseable interface, then you can try-with-resource statements without any issues. That’s what most Java standard resources do. When you open the resource in the try clause, it will get automatically closed after the try block got executed, or an exception handled.

public void automaticallyCloseResource() {
    File file = new File("./tmp.txt");
    try (FileInputStream inputStream = new FileInputStream(file);) {
        // use the inputStream to read a file
    } catch (FileNotFoundException e) {
        log.error(e);
    } catch (IOException e) {
        log.error(e);
    }
}

 

#3 – Prefer Specific Exceptions

It’s always a better option to throw a more specific exception. This will always help you in future if you need to call your method and handle the exception. This is gonna help your co-worker also who doesn’t know your code.

Therefore make sure to provide as much information as possible that it makes your API easier to understand and implement. These information blocks are gonna help the caller of your method to handle the exception in better way and avoid any additional checks.

So, always try to find the class that fits best to your exceptional event, e.g. throw a NumberFormatException instead of an IllegalArgumentException. And avoid throwing an unspecific Exception.

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

 

#4 – Document the Exceptions You Specify

You should heavily use Javadoc to document the exception which are specified in your method signature. This is similar to the above best practice: Provide as much information you can, so that the user of your method can handle or avoid the exceptions.

Make sure to add a @throws declaration to your Javadoc and to describe the situations that can cause the exception.

/**
 * This method does something extremely useful ...
 *
 * @param input
 * @throws MyBusinessException if ... happens
 */
public void doSomething(String input) throws MyBusinessException {
    ...
}

 

#5 – Throw Exceptions With Descriptive Messages

The logic of this best practice is very similar to the two previous ones. With this best practice, the information is not provided in a documented form. Instead, the exception’s message is the one which is read by everyone who understands what exactly has happened when the exception has been reported.

Therefore, the problem should be described as precise as you can. Also you should provide the most relevant information to understand the exceptional event.

This dosn’t mean that you write a paragraph of text. You should explain the reason for the exception in short sentences, 1-2 short sentences. These sentences will help your team to understand the severity of the problem, and it also makes it easier for you to analyze any service incidents.

The class name for a specific exception thrown will most likely describe the kind of error. So you don’t have to worry about providing a lot of additional information. One very good example for this is the NumberFormatException. This exception is thrown by the constructor of the class java.lang.Long if you provide a String in a wrong format.

try {
    new Long("dj");
} catch (NumberFormatException e) {
    log.error(e);
}

The name, NumberFormatException class is already telling you the kind of problem. Now you only need to provide the input string that has caused this problem. Now, if the exception class name isn’t that expressive, then in that case you need to provide more information in your message.

11:14:30,680 ERROR TestExceptionHandling:22 - java.lang.NumberFormatException: For input string: "dj"

 

#6 – Catch the Most Specific Exception First

IDEs are getting intelligent enough to help you with this best practice. They will report code block which is unreachable when you try to catch the less specific exception first.

You should always try to catch the most specific exception class first and the add less specific to the end of the list. The problem that this statement solves is that only the first catch block that matches the exception get executed. Lets take an example of the above problem specified with NumberFormatException. If you try to catch this error with IllegalArgumentException first, then you will never reach the catch block specified for NumberFormatException because it’s a subclass of the IllegalArgumentException.

So, you should always catch the specific exceptions first and the less specific catch blocks to the end of your list.

Here is an example to showcase this scenario of catching the specific exception first and the less specific later. The first catch block handles all NumberFormatExceptions and the second one all IllegalArgumentExceptions which are not a NumberFormatException.

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

 

#7 – Don’t Catch Throwable

You should never use Throwable in the catch block although you can use it in a catch clause. Let’s understand this in more details.

Throwable is the superclass of all exceptions and errors. So, if you use Throwable in a catch block, then it will not only catch your exceptions but also all errors. In general, errors are thrown by the JVM to indicate serious problems which are not intented to be handled by an application. OutOfMemoryError or the StackOverflowError are some of the typical examples for the same. Both of these are caused by situations that are outside of the control of the applicatino and can’t be handled by it.

So, it’s always advisable that you should don’t catch a Throwable unless you’re absolutely sure that you’re in an exceptional situation in which you’re able or required to handle an error.

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

 

#8 – Don’t Ignore Exceptions

After analyzing your bug report you might wonder that only the first part of your use case got executed. This is generally caused by an ignored exception. Sometimes the developers are so sure that this type of exception will never be thrown and they add a catch block that doesn’t handle or log it. Instead you will find one of the famous comments that “This will never happen”.

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

Sometimes while analyzing the problems there might be situation where impossible could happen. So, it’s always advisable that you should never ignore exceptions. You cannot always be sure that your code will run smoothly forever. What will happen if someone will change the code in the future or might remove the validation that prevented the exceptional event without recognizing that this part might create a problem. Sometimes a change in the code might throw multiple exceptions of the same class, and the calling code doesn’t prevent all of them.

It’s always a best practice to write a small log message which should tell everyone that the unthinkable just had happened and someone might need to check it out.

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

 

#9 – Don’t Log and Throw

A lot of code snippets and libraries can be found where an exception is caught, logged and rethrown.

try {
    new Long("dj");
} catch (NumberFormatException e) {
    log.error(e);
    throw e;
}

You might feel intuative to log an exception and rethrow the same so that the caller of the method can handle it more appropriately. But this will produce multiple error messages for the same exception.

11:14:30,680 ERROR TestExceptionHandling:22 - java.lang.NumberFormatException: For input string: "dj"
Exception in thread "main" java.lang.NumberFormatException: For input string: "dj"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.(Long.java:965)
at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:20)
at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)

The additional messages don’t add any valuable information. As explained in best practice #5, the exception message should describe the exceptional event. And the stack trace tells you in which class, method, and line the exception was thrown.

However, if you want to any additional information then you should catch the exception and wrap it in a custom exception. For this please make sure to follow the best practice #10.

public void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e);
    }
}

So, only catch an exception if you want to handle it. Otherwise, specify it in the method signature and let the caller take care of it.

 

#10 – Wrap the Exception Without Consuming it

For some specific type of exceptions, like application or framework specific business exception, it’s adviced to catch the standard exception and wrap it into a custom one. This allows you to add any additional information and you can also implement a special handling for your exception class.

But make sure to set the original exception as the main cause. The parent class of all exceptions, Exception class, provides a specific constructor methods that accept a Throwable as a parameter. This will help in keeping the message and the stack trace of the original message, otherwise it will become difficult to analyze the exceptional event that has caused your exception.

public void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e);
    }
}

 

Conclusion

In this journal, we have tried to show a lot of different things you should consider whenever you throw or catch an exception. Most of these practices improves your code readibility and also the usability of your API.

Exceptions are most often an error handling mechanism and a communication medium at the same time. As a general advice you should make sure to discuss the best practices that you want to apply with your team so that everyone understands the general concepts and uses them in the same way.

Get 30 minute iPhone repair with Puls! Schedule your repair now!

Subscribe to our Journal

Join our mailing list to receive the latest news and updates from our team.

You have Successfully Subscribed!