Source:ww7.rowkey.me/

Handling exceptions in Java is not a simple matter. It’s not just beginners who are hard to understand; even experienced developers spend a lot of time thinking about how to handle exceptions, including which exceptions to handle, how to handle them, and so on.

This is why most development teams have rules that govern how exceptions are handled. These specifications are often quite different from team to team.

This article presents several best practices for exception handling that are used by many teams.

1. Clean up resources in Finally blocks or use try-with-resource statements

When using a resource like InputStream that needs to be used and closed, a common mistake is 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); }}Copy the code

There is no problem with the above code running without any exceptions. But when the statement in the try block throws an exception or the code it implements throws an exception, the final closing statement is not executed and the resource cannot be freed.

It makes sense to put all the clean code ina finally block or use a try-with-resource statement.

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

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


Copy the code

2. Specify an exception

Declare methods with the most specific exceptions possible to make the code easier to understand.

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


Copy the code

As shown above, NumberFormatException is literally a number formatting error.

3. Document the exception

It also needs to be documented when a method is declared to throw an exception. As with the previous point, the purpose is to give the caller as much information as possible so that exceptions can be better avoided/handled. Ten best practices for exception handling are also recommended in this article.

Add a throws declaration to Javadoc and describe the scenario in which an exception is thrown.

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


Copy the code

4. Include description information when throwing an exception

When an exception is thrown, you need to describe the problem and related information as accurately as possible, so that it can be easily read in logs or monitoring tools to better locate specific error information and the severity of the error.

This is not meant to be a lengthy description of the error message, because the class name of Exception reflects the cause of the error, so only one or two sentences are needed.

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


Copy the code

NumberFormatException tells you that the exception is a formatting error, and all you need to do is provide the error string for additional information about the exception. When the name of the exception is not obvious, you need to provide as specific an error message as possible.

5. Catch the most specific exceptions first

Many ides now intelligently suggest this best practice, suggesting unreachable code when you try to catch the most sweeping exceptions first. When there are multiple catch blocks, only the first matched catch block is executed in the catch order. Therefore, if you catch IllegalArgumentException first, the capture of NumberFormatException will not run.

public void catchMostSpecificExceptionFirst() {
    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 class of all exceptions and errors. You can catch in a catch statement, but never. If the throwable is caught, not only will all exceptions be caught, but errors will also be caught. An error is a JVM error that cannot be recovered. So do not capture throwable unless you are absolutely sure you can handle or are required to handle errors.

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

7. Don’t ignore exceptions

Many times, developers are confident that they will not throw an exception, so they write a catch block, but do nothing about it or log it.

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


Copy the code

But the reality is that often unexpected exceptions occur or it is not certain that the code will change in the future (by removing code that prevents an exception from being thrown), and because the exception is caught, there is not enough error information to locate the problem. It is reasonable to at least record the exception.

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

8. Do not log and throw exceptions

You can find a lot of code and even class libraries that have logic to catch exceptions, log them, and throw them again. As follows:

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


Copy the code

The logic seems reasonable. But this often outputs multiple logs for the same exception. As follows:

17:44:28, 945 ERROR TestExceptionHandling: 65 - Java. Lang. A 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

As shown above, no more useful information is appended to the later logs. If you want to provide more useful information, you can wrap the exception as a custom 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

Therefore, catch exceptions only when you want to handle them, otherwise you simply declare them in the method signature for the caller to handle.

9. Do not discard the original exception when wrapping exceptions

It is a common practice to catch standard exceptions and wrap them as custom exceptions. This allows you to add more specific exception information and to do specific exception handling.

It is important to note that when wrapping an Exception, it is important to set cause to the original Exception (Exception has a constructor that can pass cause). Otherwise, the loss of the original exception information makes error analysis difficult.

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