Whether you’re a novice or an experienced programmer, it’s always a good idea to review the practice of exception handling, as this will ensure that you and your team can handle problems when they occur.

Handling exceptions in Java is not easy. Exceptions are hard for beginners to understand, and even experienced developers can spend hours debating whether to throw or handle exceptions.

This is why most development teams have their own exception handling specifications. If you are new to a team, you may find that these specifications are very different from the ones you used.

Nevertheless, there are some best practices that are followed by most teams. Here are nine of the most important practices to help you get started or improve your exception handling skills.

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

In real development, you will often encounter situations where you use a resource in a try, such as an InputStream, and you need to close it after using it. A common mistake in this case is to close the resource at the end of the try.

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

The problem with this case is that as long as the exception is not thrown, the program runs fine. All code in the try will execute normally and the resource will be closed.

But there’s always a reason to use try. When you call one or more methods that might throw an exception or throw an exception yourself, the program may not reach the end of the try. So in the end, the resource will not be closed.

Because, you should either put all of your cleanup code in finally, or use try-with-resource statements.

Using Finally

In contrast to a try, code in Finally is always executed either after the code ina try is successfully executed or after an exception is handled ina catch. Therefore, you can be sure that all resources that have been opened will be closed.

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); }}}}Copy the code

Java 7 try-with-resource statements

You can also choose try-with-resource statements, which are covered in more detail in my introduction to Java exception handling.

If you implement the AutoCloseable interface in your resource, you can use the try-with-resource statement, which is what most standard Java resources do. If you open a resource in a try-with-resource, the resource will be closed automatically after the code in the try is executed or an exception is 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); }}Copy the code

2. Throw more specific exceptions

The more specific and explicit the exception you throw, the better. Keep this in mind, especially if you have a colleague who doesn’t know your code, or if you need to call your own methods and handle exceptions a few months later.

Therefore, you need to make sure that you provide as much information as possible to make your API easier to understand. This way, the person calling your method can handle the exception better and avoid extra checks like this.

So, find the class that best matches your exception event, such as throwing a NumberFormatException instead of an IllegalArgumentException. For example, when converting an argument to a numeric value fails, you should throw a specific NumberFormatException instead of a generic IllegalArgumentException). Avoid throwing an unspecific exception.

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

3. Document your exceptions

When you specify an exception in a method signature, you should also log it in Javadoc.

So be sure to add the @throws declaration in Javadoc and describe the circumstances that might cause an exception.

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

4. Throw the description along with the exception

The idea behind this approach is similar to the first two. But this time, you don’t have to give information to your method callers. For anyone who encounters an exception error and needs to figure out why, the exception information is always logged or printed on the screen at the same time as the exception occurs.

Therefore, be as precise as possible, and it is best not to use Throwable in a catch unless you are sure that you are in a certain situation where you can handle an error yourself or are asked to handle an error. Describe the exception event and provide the most relevant information so that others can understand what the exception is.

Don’t get me wrong. You don’t have to write a long paragraph, but you should explain in one or two short sentences why the exception happened. This will help your development team understand the seriousness of the problem and make it easier to analyze service incidents.

If you throw a specific exception, its class name probably already describes what kind of error it is. So, you don’t need to provide a lot of additional descriptive information. A good example is the java.lang.Long constructor that throws a NumberFormatException when you provide an ill-formed String argument.

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

The class name of NumberFormatException already tells you the type of problem. So the exception message just needs to return the input string that caused the problem. If the name of the exception class does not indicate what it means, you also need to provide the necessary explanatory information in the exception message.

17:17:26, 386 ERROR TestExceptionHandling: 52 - Java. Lang. A NumberFormatException: For input string: "xyz"Copy the code

5. Catch specific exceptions first

Most ides can do this for you. When you try to catch less specific exceptions first, the IDE reports that this is an unreachable block of code.

The reason for this is that only the first catch block that matches an exception is executed. So, if you catch an IllegalArgumentException first, you’ll never get to the catch block that handles the more specific exception NumberFormatException, Because NumberFormatException is a subclass of IllegalArgumentException.

So, catch more specific exceptions first, and leave less specific catch blocks later.

You can see an example of such a try-catch statement below. The first catch handles all NumberFormatExceptions, and the second catch handles illegalArgumentException exceptions other than NumberFormatException.

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. While you can use it in the catch clause, you should never do so!

If you use Throwable in a catch clause, it will catch not only all exceptions, but also all errors. These errors are thrown by the JVM to indicate serious errors that are not intended to be handled by the application. Typical examples are OutOfMemoryError and StackOverflowError, both of which are caused by conditions that are beyond the control of the application and cannot be handled.

Therefore, it is best not to use Throwable in a catch unless you are sure that you are in a certain situation, such as being able to handle errors yourself or being asked to handle errors.

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

7. Don’t ignore exceptions

Have you analyzed bug reports where only the first part of the use case is executed?

This is usually caused by ignoring exceptions. A developer may be quite sure that the exception will not be thrown, and then add a catch that cannot handle or log the exception. When you find This catch, you’ll probably find the famous note “This will never happen”.

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

Yes, you could be analyzing a problem that will never happen.

So make sure you don’t ignore exceptions. You don’t know how the code will change in the future. Some people may mistakenly delete validation for exceptional events without realizing that this could cause problems. Or the code that throws the exception has been modified, and the same class throws multiple exceptions, and the code that calls them doesn’t prevent them from happening.

At the very least, you should print out the log message and tell anyone who made a mistake unintentionally to check it out.

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

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

This is probably the most overlooked rule of practice in this article. You can find this problem in many code snippets and even libraries, where exceptions are caught, printed, and rethrown.

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

It might be intuitive to see the exception being printed, the exception being rethrown, and the caller handling it just fine. However, this can result in multiple error messages being printed with the same exception.

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

Additional information does not provide additional details about the error. As stated in rule 4, the exception information should accurately describe the exception event. The Stack Trace tells you which class, method, and line the exception was thrown in.

If you need to add additional information, you should capture and wrap the exception in a custom exception, but be sure to follow the rule of Practice 9 below.

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

So, catch an exception only if you want to handle it. Otherwise, it would be nice to indicate the exception in the method signature for the caller to notice.

9. Wrap exceptions without discarding the original

Sometimes it is better to wrap an exception as a custom exception than to catch a standard exception. A typical example is a business-specific exception to an application or framework. This allows you to add additional information and implement a specific handling method for your exception class.

When you do this, make sure the original exception is set to cause. The Exception class provides a set of specialized constructors that can accept Throwable as an argument. Otherwise, you will lose the stack trace and information of the original exception, making it very 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

conclusion

As you can see, there are many things to consider when deciding whether to throw or catch an exception. Most of the above practices are intended to improve the readability and usability of your code and APIS.

Exceptions are not only an error handling mechanism, but also a communication medium. Therefore, you should discuss with your colleagues what are the best practices and guidelines you want to apply so that everyone understands the underlying concepts and applies them in the same way.

Recommended reading

Docker Core Technology Video tutorial

Java logging system summary in detail

【 resources sharing 】Spring Cloud micro-service combat video course

Docker installation and common commands & operations

Java Video data collection

Netty Video tutorial

Springmvc source code analysis summary

Spring IOC architecture diagram and AOP knowledge brain diagram

MyBatis architecture source code interpretation

Are you all using IntelliJ IDEA? Maybe you should read this blog post

Our official account will give you benefits from time to time, including learning resources, etc. Please look forward to it!

If you can’t use the content in your work now, you can forward it to your friend circle or favorite first, so that you can find it easily when you use it.

In addition, we welcome public accounts to reply to wechat, add wechat friends, and learn and communicate with each other.