📜 ⬆️ ⬇️

Fix 7 common exception handling errors in java

Hi, Habr! I present to you the translation of the article Fixing 7 Common Java Exception Handling Mistakes by Thorben Janssen.

Handling an exception is one of the most common, but not necessarily one of the simplest tasks. This is still one of the frequently discussed topics in experienced teams, and there are several advanced techniques and common mistakes you should be aware of.

Here are a few things to avoid when handling exceptions in your application.

Error 1: declaration java.lang.Exception or java.lang.Throwable


As you already know, you need to either declare or handle the checked exception. But checked exceptions are not the only ones you can specify. You can use any subclass of java.lang.Throwable in the throws clause. Thus, instead of specifying two different exceptions thrown by the following code snippet, you can simply use the java.lang.Exception exception in the throws clause.
')
public void doNotSpecifyException() throws Exception { doSomething(); } public void doSomething() throws NumberFormatException, IllegalArgumentException { // do something } 

But this does not mean that you should do this. Specifying Exeption or Throwable makes it almost impossible to properly handle them when calling your method. The only information that the method that you are calling receives is that something can go wrong. But you do not share any information about any exceptional events that may occur. You hide this information for generalized reasons for throwing exceptions. It becomes even worse when your application changes over time. Throwing out generalized exceptions hides all exception changes that the caller must expect and handle. This can lead to several unexpected errors that need to be found in the test case instead of a compiler error.

Use specific classes


It is much better to specify the most specific exception classes, even if you have to use several of them. This tells the calling device which exceptional events to handle. It also allows you to update the throw clause when your method throws an additional exception. This way, your clients are aware of the changes and even get an error if you change the exceptions thrown. Such an exception is much easier to find and handle than an exception that appears only when you run a specific test case.

 public void specifySpecificExceptions() throws NumberFormatException, IllegalArgumentException { doSomething(); } 

Error 2: interception of generalized exceptions


The severity of this error depends on which software component you are implementing and where you find the exception. It might be nice to catch a java.lang.Exception in the main method of your Java SE application. But you should prefer to catch certain exceptions if you are implementing a library or working on deeper layers of your application.

This has several advantages. This approach allows you to handle each exception class differently and does not allow you to catch exceptions that you did not expect.

But keep in mind that the first catch block that handles the exception class or one of its superclasses will catch it. Therefore, be sure to catch the most specific class first. Otherwise, your IDE will show an error message or a warning about an unreachable block of code.

 try { doSomething(); } catch (NumberFormatException e) { // handle the NumberFormatException log.error(e); } catch (IllegalArgumentException e) { // handle the IllegalArgumentException log.error(e); } 

Error 3: Logging and Forwarding Exceptions


This is one of the most popular errors when handling Java exceptions. It may seem logical to register an exception where it was thrown, and then forward it to the caller, which can implement specific processing for a particular use case. But you should not do this for three reasons:

1. You do not have enough information about the use case that the caller of your method wants to implement. An exception may be part of the expected behavior and handled by the client. In this case, there is no need to register it. This will add a false error message to the log file, which should be filtered by your operations team.

2. The journal message does not provide any information that is not yet part of the exception itself. Its trace and stack trace should contain all the necessary information about an exceptional event. The message describes this, and the stack trace contains detailed information about the class, method, and line in which it occurred.

3. You can register the same exception several times when you register it in every catch block that catches it. This will ruin the statistics in your monitoring tool and make it difficult to read the log file for your operations and the development team.

Log the exception where you handle it.


Thus, it is best to register an exception when you handle it. As in the following code snippet. The doSomething method throws an exception. The doMore method simply indicates it, because the developer does not have enough information to process it. It is then processed in the doEvenMore method, which also writes a log message.

 public void doEvenMore() { try { doMore(); } catch (NumberFormatException e) { // handle the NumberFormatException } catch (IllegalArgumentException e) { // handle the IllegalArgumentException } } public void doMore() throws NumberFormatException, IllegalArgumentException { doSomething(); } public void doSomething() throws NumberFormatException, IllegalArgumentException { // do something } 

Error 4: Using Exceptions to Control Flow


Using exceptions to control the flow of your application is considered anti-pattern for two main reasons:

They basically work as a Go To statement, because they cancel the execution of the code block and go to the first catch block that handles the exception. This makes the code very difficult to read.

They are not as effective as general Java control structures. As the name implies, you should only use them for exceptional events, and the JVM does not optimize them in the same way as other code. Thus, it is better to use the right conditions to break your loops or if-else instructions to decide which blocks code must be executed.

Error 5: remove the cause of the exception


Sometimes you may need to wrap one exception into another. Your team may have decided to use a special business exception with error codes and single processing. There is nothing wrong with this approach if you do not eliminate the cause.

When you create a new exception, you should always set the initial exception as the reason. Otherwise, you lose track of the message and the stack, which describe the exceptional event that caused your exception. The Exception class and all its subclasses provide several constructor methods that take the original exception as a parameter and set it as the cause.

 try { doSomething(); } catch (NumberFormatException e) { throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR); } catch (IllegalArgumentException e) { throw new MyBusinessException(e, ErrorCode.UNEXPECTED); } 

Error 6: Generalization of Exceptions


When you generalize an exception, you catch a specific, for example, NumberFormatException, and instead generate a nonspecific java.lang.Exception. This is similar, but even worse than the first error I described in this article. It not only hides information about a specific error case on your API, but also makes access difficult.

 public void doNotGeneralizeException() throws Exception { try { doSomething(); } catch (NumberFormatException e) { throw new Exception(e); } catch (IllegalArgumentException e) { throw new Exception(e); } } 

As you can see in the following code snippet, even if you know what exceptions a method can cause, you can't just catch them. You need to catch the generic Exception class and then check the type of its cause. This code is not only cumbersome to implement, but also difficult to read. It gets even worse if you combine this approach with error 5. This removes all the information about the exceptional event.

 try { doNotGeneralizeException(); } catch (Exception e) { if (e.getCause() instanceof NumberFormatException) { log.error("NumberFormatException: " + e); } else if (e.getCause() instanceof IllegalArgumentException) { log.error("IllegalArgumentException: " + e); } else { log.error("Unexpected exception: " + e); } } 

So which is the best approach?

Be specific and save the reason for the exception.


The exceptions that you throw should always be as specific as possible. And if you wrap an exception, you must also set the original exception as the reason not to lose the stack trace and other information describing the exceptional event.

 try { doSomething(); } catch (NumberFormatException e) { throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR); } catch (IllegalArgumentException e) { throw new MyBusinessException(e, ErrorCode.UNEXPECTED); } 

Error 7: adding unnecessary exception conversions


As I explained earlier, it can be useful to wrap the exceptions in custom ones, if you set the original exception as the reason. But some architects overdo it and introduce a special exception class for each architectural level. Thus, they catch an exception in the level of persistence and transfer it to MyPersistenceException. The business layer catches and wraps it in a MyBusinessException, and it continues until it reaches the API level or is processed.

 public void persistCustomer(Customer c) throws MyPersistenceException { // persist a Customer } public void manageCustomer(Customer c) throws MyBusinessException { // manage a Customer try { persistCustomer(c); } catch (MyPersistenceException e) { throw new MyBusinessException(e, e.getCode()); } } public void createCustomer(Customer c) throws MyApiException { // create a Customer try { manageCustomer(c); } catch (MyBusinessException e) { throw new MyApiException(e, e.getCode()); } } 

It is easy to see that these additional exception classes offer no advantages. They simply introduce additional layers that wrap the exception. And although it would be fun to wrap a gift in a variety of colorful paper, this is not a very good approach to software development.

Be sure to add information


Just think about the code that should handle the exception or about yourself when you need to find the problem that caused the exception. First you need to break through several levels of exceptions to find the root cause. And until today, I have never seen the application that used this approach, and added useful information with each layer of the exception. They either summarize the error message and code, or provide redundant information.

Therefore, be careful with the number of custom exception classes that you enter. You should always ask yourself if the new exception class provides additional information or other benefits. In most cases, to achieve this, you do not need more than one level of user exceptions.

 public void persistCustomer(Customer c) { // persist a Customer } public void manageCustomer(Customer c) throws MyBusinessException { // manage a Customer throw new MyBusinessException(e, e.getCode()); } public void createCustomer(Customer c) throws MyBusinessException { // create a Customer manageCustomer(c); } 

Source: https://habr.com/ru/post/337536/


All Articles