The original | dzone.com/articles/9-…

The author | Thorben Janssen

Translation | geekymv

Whether you’re a beginner or an experienced developer, improving your exception handling skills can help you and your team solve problems better.

Exception handling in Java is not easy and can be difficult for beginners to understand, and even experienced developers can spend hours debating how to throw or which exceptions to handle.

That’s why most development teams have their own set of rules for how to use them. If you’re new to a team, you might be surprised at how different the rules are from the ones you’ve used before.

Nevertheless, there are some best practices that most teams use. Here are nine of the most important ways to start or improve exception handling.

1. Clean up resources ina finally block or use try-with-resource statements

Often you will use a resource, such as InputStream, in a try block and need to close it later. A common mistake in this case is to close the resource at the end of the try block.

public void doNotCloseResourceInTry(a) {
    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); }}Copy the code

The problem is, this works fine as long as you don’t throw an exception. The statement in the try block will be executed and the resource will be closed.

But you add a try block for a reason, you call one or more methods that might throw an exception, or you might throw an exception yourself, which means you might not get to the end of the try block, and ultimately, you can’t close the resource.

Therefore, you should put all cleanup code ina finally block or use try-with-resource statements.

Use the finally code block

Unlike the last few lines of a try block, a finally block is always executed. This happens after a try block executes successfully, or after an exception is handled in a catch block. Therefore, you can be sure to clean up all open resources.

public void closeResourceInFinally(a) {
    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); }}}}Copy the code
The try-with-resource statement in Java7

The alternative is the try-with-Resource statement, which I describe in more detail in the article introducing Java exception handling.

This can be used if your resource implements the AutoCloseable interface. This is what most Java standard resources do. When you open a resource in the try clause, it closes automatically after the try block executes or an exception occurs.

public void automaticallyCloseResource(a) {
    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); }}Copy the code

2. Prefer specific exceptions

The more specific the exception thrown, the better. Keep in mind that a colleague who doesn’t know your code, or you months from now, will need to call your methods and handle exceptions.

So make sure you give them as much information as possible. This makes your API easier to understand. Eventually, the caller of a method will be better able to handle exceptions or avoid them with extra checking.

So always try to find the class that best fits your exception event, for example, throwing NumberFormatException instead of IllegalArgumentException. Avoid throwing an unspecified exception.

public void doNotDoThis(a) throws Exception {... }public void doThis(a) throws NumberFormatException {... }Copy the code

3. Document the specified exception

Whenever you specify an exception on a method signature, you should document it in your Javadoc. This has the same goal as previous best practices: give the caller as much information as possible so he can avoid or handle exceptions.

Therefore, be sure to add the @throws declaration to your Javadoc and describe situations that could cause exceptions.

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

4. Throw an exception with descriptive information

The idea behind this best practice is similar to the previous two, except that you don’t have to provide information to method callers. Anyone who needs to know about an exception recorded in a log file or monitoring tool can read the exception information.

Therefore, you should describe the problem as accurately as possible and provide the most relevant information to understand the abnormal events.

Don’t get me wrong, you should not write a paragraph of text, but explain the reason for the exception in 1-2 short sentences. This will help your operations team understand the severity of the problem and make it easier to analyze any service incidents.

If a specific exception is thrown, its class name most likely already describes the type of error. Therefore, you don’t need to provide a lot of additional information. A good example is NumberFormatException. When you provide an incorrect string format, the java.lang.Long constructor throws a NumberFormatException.

try {
    new Long("xyz");
} catch (NumberFormatException e) {
    log.error(e);
}
Copy the code

The name of the NumberFormatException class already tells you the type of problem. Its information only needs to provide the input string that caused the problem. If the name of the exception class is less expressive, you need to provide the necessary information in the message.

17:17:26.386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"
Copy the code

You can see how important it is to give a class a good name.

5. Prioritize catching the most specific exceptions

Most ides can help you implement this best practice. When you try to catch less specific exceptions, they report an unreachable block of code.

The problem is that only the first catch block that matches the exception is executed. So if you catch an IllegalArgumentException exception first, you won’t get to the catch block that should handle the more specific NumberFormatException exception. Because it is a subclass of the IllegalArgumentException class.

Always catch the most specific exception first, and then add the less specific catch block to the end of the list.

In the code snippet below, you can see an example of a try-catch statement. The first catch block handles all numberFormatExceptions, And the second catch block handles all illegalArgumentExceptions that are not part of NumberFormatException.

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

6. Do not capture Throwable

Throwable is the parent of all Exception and Error classes. You can use it in catch statements, but you should never do it!

If you use Throwable in a catch statement, it will catch not only all exceptions, but also all errors.

Error is thrown by the JVM and represents a serious problem that cannot be handled by the application. Typical examples are OutofMemoryErrors or StackOverflowErrors, which are caused by conditions beyond the control of the application and cannot be handled.

Therefore, it is best not to capture Throwable unless you are absolutely sure that you are in a special situation where you can or need to handle errors.

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

7. Don’t ignore exceptions

Have you ever analyzed a bug report where only the first part of the use case was executed?

This is usually caused by ignoring an exception, the developer might be pretty sure it never throws, and add a catch block that doesn’t handle or print logs, and when you find This block, you might even find a famous comment “This will never happen”.

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

Well, you might be analyzing a problem that’s not going to happen.

Therefore, do not ignore exceptions. You don’t know how the code will change in the future. Someone might remove validations that prevent exception events without realizing that this would cause problems. Or the code that throws an exception is changed so that multiple exceptions of the same class are now thrown, and the calling code does not prevent them all.

You should write at least one log message telling everyone that something unexpected has just happened and someone needs to check it out.

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

8. Do not print logs and throw exceptions at the same time

This is probably the most overlooked best practice on the list. You can find exceptions caught, logged, and rethrown in many code snippets and even libraries.

try {
    new Long("xyz");
} catch (NumberFormatException e) {
    log.error(e);
    throw e;
}
Copy the code

It might be intuitive to record the exception that occurred and then rethrow it so that the caller can handle it appropriately. But it will write multiple error messages for the same exception.

17:44:28.945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"
Exception in thread "main" 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.(Long.java:965)
at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
Copy the code

The additional messages didn’t add anything. As described in Best Practice 4, exception information should describe exception events. The stack trace tells you which class, method, and line the exception was thrown in.

If you need to add additional information, you should catch the exception and wrap it in a custom exception, but be sure to follow best Practice 9.

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

Wrap an exception without using it

Sometimes it is best to catch an exception and wrap it in a custom exception. A typical example of such an exception is a business-specific exception for an application or framework. This allows you to add additional information and also implement special handling for exception classes.

When you do this, be sure to set the original exception to cause. The Exception class provides a concrete constructor that accepts the Throwable argument. Otherwise, you lose the stack trace and information about the original exception, which makes it difficult to analyze the event that caused the exception.

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

Constructor of the Exception class with the Throwable argument.

public Exception(String message, Throwable cause) {
    super(message, cause);
}
Copy the code

conclusion

As you can see, there are many different things you should consider when you throw or catch an exception. The goal of most of them is to improve code readability or API usability.

Exceptions are usually an error-handling mechanism as well as a communication mechanism. Therefore, you should make sure to discuss the best practices and rules you want to apply with your colleagues so that everyone understands the general concepts and uses them in the same way.