9 Best Practices for Java exception handling

Posted by hjunw on Tue, 01 Mar 2022 11:22:00 +0100

Source: http://t.cn/ESocliD

    1. Clean up resources in Finally or use the try with resource statement
  • Use Finally

  • Try with resource statement in Java 7

    1. Give accurate exception handling information
    1. Record the exceptions you specified
    1. Throw an exception using a descriptive message
    1. Catch specific exceptions first
    1. Do not use Throwable in catch
    1. Don't ignore Exceptions
    1. Do not record and throw an exception
    1. Abnormal packaging
  • summary

In this paper, the author introduces nine best methods and practices for handling exceptions, which are combined with examples and code display to make developers better understand these nine methods, and guide readers to choose different exception handling methods in different situations.

The following is the Translation:

Exception handling in Java is not a simple topic. It's hard for beginners to understand, and even experienced developers spend hours discussing how to throw or handle these exceptions.

This is why most development teams have their own rules and methods for exception handling. If you are new to a team, you may be surprised how different these methods are from those you have used before.

However, there are several best methods of exception handling that are used by most development teams. Here are the 9 most important ways to help improve exception handling.

1. Clean up resources in Finally or use the try with resource statement

===========================================

Usually, you use a resource in a try, such as InputStream, and then you need to close it. In this case, a common mistake 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);

     }

 }


The problem is that this method works well as long as no exceptions are thrown. All statements in try will be executed and resources will be closed.

But you call one or more methods that may throw exceptions in try, or throw exceptions yourself. This means that the end of the try may not be reached. Therefore, these resources will not be closed.

Therefore, you should put the code to clean up resources into Finally, or use the try with resource statement.

Use Finally

Compared with try, the content in Finally will be executed after the code in try is successfully executed or an exception is handled in catch. Therefore, you can ensure that all open resources are cleaned up.

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

             }

         }

     }

 }


Try with resource statement in Java 7

Another option is the try with resource statement, which is described in more detail in the introduction to Java exception handling.

If your resource implements the autoclosable interface, you can use it, which is what most Java standard resources do. When you open a resource in the try clause, it will automatically close after the try is executed, or handle an exception.

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

     }

 }


2. Give accurate exception handling information

==============

The more specific the exception you throw, the better. Remember that a colleague who doesn't know much about your code may need to call your method and handle this exception in a few months.

Therefore, please make sure to provide as much information as possible, which will make your API easier to understand. Therefore, the caller of your method will be able to handle the exception better or avoid it through additional checks.

Therefore, try to better describe your exception handling information. For example, use NumberFormatException instead of IllegalArgumentException to avoid throwing an unspecified exception.

 public void doNotDoThis() throws Exception {

     ...

 }

 public void doThis() throws NumberFormatException {

     ...

 }


3. Record the exceptions you specified

============

When you specify an exception in a method, you should record it in the Javadoc. This has the same goal as the methods mentioned earlier: to provide callers with as much information as possible so that they can avoid exceptions or handle exceptions more easily.

Therefore, make sure to add a @ throws declaration in Javadoc and describe the possible exceptions.

 /**

  \* This method does something extremely useful ...

  *

  \* @param input

  \* @throws MyBusinessException if ... happens

  */

 public void doSomething(String input) throws MyBusinessException {

     ...

 }


4. Throw an exception using a descriptive message

==============

The concept of this best practice is similar to the first two. But this time, you don't have to provide information to the person calling the method. The exception message will be read by everyone, and you must know what happens when an exception is reported in the log file or monitoring tool.

Therefore, the problem should be described as accurately as possible and relevant information should be provided to understand the abnormal events.

Don't get me wrong. You don't need to write a paragraph, but you should use 1-2 short sentences to explain the cause of the abnormality. This helps the development team understand the severity of the problem and makes it easier for you to analyze any service event.

If a specific exception is thrown, its class name probably already describes this type of error. So you don't need to provide a lot of additional information. A good example is that when you use a string in the wrong format, such as NumberFormatException, it will be used by the Java class Thrown by the constructor of lang.long.

 try {

     new Long("xyz");

 } catch (NumberFormatException e) {

     log.error(e);

 }


NumberFormatException already tells you the type of problem, so you only need to provide the input string that causes the problem. If the name of the exception class is not expressive, you need to provide the necessary explanation information.

17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"

5. Catch specific exceptions first

============

Most ides can help you do this. When you try to catch an uncertain exception, it will report an unreachable block of code.

The problem is that only the first catch statement that matches the exception will be executed, so if you find the IllegalArgumentException first, you will never go to the catch to deal with the more specific NumberFormatException, because it is a subclass of IllegalArgumentException.

Therefore, we should first catch a specific exception class and add some catch statements to deal with non-specific exceptions at the end.

You can see an example of such a try catch statement in the following code snippet. The first catch handles all NumberFormatExceptions exceptions, and the second catch handles illegalargumentexception exceptions other than NumberFormatException exceptions.

 public void catchMostSpecificExceptionFirst() {

     try {

         doSomething("A message");

     } catch (NumberFormatException e) {

         log.error(e);

     } catch (IllegalArgumentException e) {

         log.error(e)

     }

 }


6. Do not use Throwable in catch

==========================

Throwable is the parent of exceptions and errors. Of course, you can use it in the catch clause, but you shouldn't.

If you use Throwable in the catch clause, it will catch not only all exceptions, but also all errors. The JVM throws errors, which is a serious problem that the application does not intend to deal with. Typical examples are OutOfMemoryError or StackOverflowError. Both cases are caused by conditions outside the control of the application and cannot be handled.

Therefore, it is best not to use Throwable in catch unless you are completely sure that you are in a special situation and you need to deal with an error.

 public void doNotCatchThrowable() {

     try {

         // do something

     } catch (Throwable t) {

         // don't do this!

     }

 }


7. Don't ignore Exceptions

==================

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

This is usually caused by an ignored exception. The developer may be quite sure that it will not be thrown and add a catch statement that cannot process or record it. When you find it, you will probably understand a famous saying "This will never happen".

 public void doNotIgnoreExceptions() {

     try {

         // do something

     } catch (NumberFormatException e) {

         // this will never happen

     }

 }


Yes, you may be analyzing an impossible problem.

So please don't ignore an exception. You don't know what the code will change in the future. Some people may delete validation that blocks abnormal events without realizing that this is causing a problem. Or the code that throws the exception is changed, and now multiple exceptions of the same class are thrown, and the calling code can't prevent all these exceptions.

You should at least write a log message to tell everyone that you need to check the problem.

 public void logAnException() {

     try {

         // do something

     } catch (NumberFormatException e) {

         log.error("This should never happen: " + e);

     }

 }


8. Do not record and throw an exception

==============

This is probably the most often overlooked. You can find in many code fragments or library files that exceptions will be caught, recorded and re thrown.

 try {

     new Long("xyz");

 } catch (NumberFormatException e) {

     log.error(e);

     throw e;

 }


It may be intuitive to log an exception when it occurs and then re throw it so that the caller can handle it appropriately. However, 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)


Do not add any additional information. As explained in Item 4 above, the exception message should describe the exception event. The stack will tell you in which class, method, and line the exception was thrown.

If you need to add additional information, you should catch the exception and wrap it in a custom message. But be sure to follow Article 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);

     }

 }


Therefore, just catch an exception you want to handle, specify it in the method, and let the caller handle it.

9. Abnormal packaging

=======

Sometimes it's best to catch a standard exception and encapsulate it in a custom exception. Typical examples of such exceptions are application or framework specific business exceptions. This allows you to add additional information and also implement a special handling for exception classes.

When you do this, be sure to reference the original Exception handling. The Exception class provides some specific constructor methods that can accept Throwable as a parameter. Otherwise, you will lose the stack trace and message of the original Exception, which will make it difficult for you 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);

     }

 }


summary

As you can see, there are many different things to consider when throwing or catching exceptions. Most of the above methods can improve code readability or API usability.

Exceptions are usually an error handling mechanism and a communication medium. Therefore, you should ensure that your colleagues discuss the best practices and methods you want to apply so that everyone understands common concepts and uses them in the same way.

Topics: Java Back-end