Exceptions were introduced in many programming languages as a standard method to report and handle errors. If you have ever used functions that return special values (usually -1 or NULL) to indicate an error, you should know how easy it is to forget to check this value and completely ignore the error. One great advantage of exceptions is that it is generally “hard to forget about them”. It means that if you don’t handle the exception somewhere in the code, it will abort the execution of the application and will also appear on a console or in logs.
Although exceptions were introduced into mainstream programming languages many years ago and many books have been written about them, they are still used wrongly. In this article I try to describe several doubtful practices regarding usage of exceptions which it is usually better to avoid.
Using too many try-catch blocks
Sometimes you may see code like this:
Writer writer = null; try { writer = new FileWriter("/tmp/a"); } catch(FileNotFoundException e) { // handle error; and return from the method } catch (IOException ex) { // handle error; and return from the method } try { writer.write("Line1"); } catch (IOException e) { // handle error; close the file and return from the method } try { writer.close(); } catch (IOException e) { // handle error; and return from the method }
Generally there is nothing really wrong with this code regarding exceptions but it would be much better if all instructions were put into single try-catch block:
Writer writer = null; try { writer = new FileWriter("/tmp/a"); writer.write("Line1"); } catch(FileNotFoundException e) { // handle error } catch (IOException ex) { // handle error } finally { if (writer != null) writer.close(); }
It reduces the length of the source code and improves its readability. Merging try-catch blocks changes the behaviour of the application but usually the change is not very significant and is totally acceptable. In some cases we may still prefer to use multiple try-catch blocks (e.g. to provide better error reporting).
Using exceptions for control flow
Exceptions were invented only for error reporting and handling and should not be used for other purposes like control flow. Here is an example of such misuse:
try { Iterator<String> it = list.iterator(); while (true) { String value = it.next(); // do something with value } } catch (NoSuchElementException e) { // OK, end of the list }
Instead of catching NoSuchElementException the code should check whether there is a next element available in the iterator before accessing it. This check would completely prevent appearance of the mentioned exception.
While the code above is ugly, it has also another problem. Exception throwing and catching is generally a very expensive operation in most (all?) programming languages. If the code above is run very often (especially in some loop), it may greatly slow down your application.
Using wrong exception class
Java provides own exception hierarchy with many predefined exception classes. The exceptions in Java can be divided into unchecked and checked ones. The former should be used for reporting programmer errors like dereferencing of null value, accessing elements outside of array bounds or fetching objects from an empty collection. Generally, unchecked exceptions can be easily avoided by adding a simple condition in the code before calling a method. The later are generally unpredictable and usually not much can be done to prevent them. The examples of checked exceptions are I/O or file parsing errors.
The common mistake is throwing checked exception from a method when unchecked one should be used. The programmers using this method will be enforced to catch the exception but there would be nothing they could do to handle it (maybe except rethrowing it as an unchecked exception). This will result in more unnecessary and hard to read code.
The opposite is also possible. If a method throws unchecked exception for an error which must be handled in some way, the programmers simply will not catch this exception which in turn may abort the execution of the application.
Meaningless messages
One of the common mistakes is throwing exception without any message or with message which does not describe the cause of the problem:
if (list.isEmpty()) throw new IllegalArgumentException(); if (list.size() != array.length) throw new IllegalArgumentException("Wrong data");
Such exceptions are generally useless. Even if you have the full source code of the class from which the exception is thrown, it may still not explain what is exactly wrong. It is generally better to put some additional data into exception message so it will be much easier to find the root cause of the exception once it appears:
if (list.isEmpty()) throw new IllegalArgumentException("List is empty"); if (list.size() != array.length) throw new IllegalArgumentException("List and array have different sizes: " + list.size() + " " + array.length);
Catching unchecked exceptions
In most cases unchecked exceptions should not be caught at all but prevented using a simple condition. You can see the example of such issue above in paragraph Using exceptions for control flow. Whenever there is a catch block for unchecked exception in the code, it should be removed and replaced by a proper condition.
Another problem with catching unchecked exceptions is that it hides programming errors and therefore makes it more difficult to find out why certain thing does not work. The same is also true when catching instances of general Exception and Throwable classes.
As always there are some situations in which catching unchecked exceptions is acceptable. The first is that there is no easy way to ensure that the exception won’t happen. For example there is no easy method to check whether the string representation of a number is valid before calling method Integer.parse(). Therefore, it is much easier to call the method and catch the unchecked exception if it happens.
The other situation is that you are running some external code which cannot be validated beforehand and you don’t want the unchecked exceptions thrown by this code to abort the execution of your application. This is what web application servers do.
Reporting exceptions late
Sometimes programmers are afraid to throw an exception whenever an error happens. Instead they often return null value, empty string, empty collection or just ignore the error. For example the method below returns null whenever it is impossible to return the correct value:
public String getSecondElement(List<String> list) { if (list.size() >= 2) return list.get(1); else return null; }
While it may be convenient, there is a risk associated with this solution. The returned null value may be passed to some other part of the code and cause there NullPointerException exception. Additionally, the place where the exception was raised may be very distant from the place where the original problem occurred which makes it much harder to find the root cause.
If we raised an exception (even unchecked one) at the first place we noticed the problem (in our case in getSecondElement method), it would be much easier to find the root cause and fix it.
Handling exceptions early
Some programmers feel obliged to catch and handle every exception in the sample place (a method or a class) where they are raised. Usually, at this place we don’t have enough knowledge about the bigger operation this method or class is a part of and therefore we cannot handle the exceptions properly. If we are unsure how to handle a particular exception, it is usually much better to pass it up to the caller because higher level methods have more knowledge about the context and can better revert or retry the operation or inform the user about the error.
Ignoring exceptions
In my opinion the worst thing we could do with exceptions is ignoring them:
public void loadDrumKit(String name) { try { // here comes code for loading from file } catch (IOException e) { // ignore it - we can use the old drum kit } }
When the exception sooner or later happens, we would have no information why the application started to misbehave. As a total minimum we should log the caught exception with full stack trace so that we could find the root cause after checking the logs. And the best way to handle the exception would be to either pass it up to the caller or clearly inform the user about the problem (of course, additional logging is welcome).
Conclusion
Handling of exceptions is never easy but still we should not misuse them and take short-cuts just because it is easier. Otherwise, it may affect the application stability and predictability which will reduce the overall customer satisfaction. Of course, the things written above are just guidelines for exception handling and there are situations where it is completely OK (or even better) to deviate from them.
Your first example of “Using too many try/catch” blocks is not necessarily a misuse, and it definitely changes the behavior of the application if you group them all together in one block. The issue with grouping them together is that you loose information about which line of code actually failed and loose the ability to give the user a more detailed error message about what actually failed. The fact that it’s less readable is just an unfortunate fact of error handling that no one has solved yet.
What generally happens when grouping the catch together is that they are place in the wrong order, if that happens you lose information or they might misbehave or they might not be caught at all.
Is usually good to have them group, it produces cleaner code.
Thank you. I have updated the article with your comments.
The performance of exceptions is more complex than you might think.
It’s often the case that the majority of time spent during exception processing is spent during the construction of the Throwable object itself. Constructing a Throwable causes the JVM to capture a stack trace, which is an expensive operation. However, there’s nothing that says you have to create a new instance of Throwable for every time you throw an exception. There’s an Oracle blog post from 2007 that shows how you can throw a pre-allocated object and bypass the stack trace capture altogether: https://blogs.oracle.com/jrose/entry/longjumps_considered_inexpensive
In this scenario, without the stack capture (and finally blocks), throwing an exception boils to identifying the target catch block and unwinding the stack. The Oracle JVM can reduce this down to a handful of machine instructions under the proper circumstances. (Switching to a different stack frame can be done simply by adjusting a few CPU registers….) Particularly in cases with deeply nested recursion, this can make throwing an Exception the fastest and clearest way to implement a change in control flow.
Thank you for the link. It is very interesting.
Great article.
I’m extremely pleased to uncover this page. I need to to thank you for ones time for this
particularly wonderful read!! I definitely appreciated every bit of it and I have
you saved as a favorite to see new stuff in your website.