Share: Facebook icon - Twitter icon - LinkedIn icon

Java - Rethrow/wrap or log exceptions!

Published Sun Oct 04 2020

Tags: java programming


This probably seems like common sense to a lot of you, but it is quite easy to forget. I try to do it in my own code, but when reviewing other peoples code I forget to think about it 90 % of the time! Sometimes you don't rethrow the exception because the cause seems clear cut in all scenarios. Some of the time, we simply throw a new exception with an error code or message, and think that if we get that exception later, we will know what caused it. That may not always be the case.

Exceptions should contain enough information for you to be able to see the reason for it happening. In the cases you start a new exception-chain based on some state in your code, then the content of this article won't apply. This article is about catching exceptions, throwing new ones and logging the information about them.

Rethrow/wrap exceptions

Rethrow/wrap in this case means to throw a new exception with the original as the cause (in other words: passing it to the constructor of our new exception). To see why this is useful, let's take a look at an example. The example might seem stupid, and I somewhat agree as I usually prefer to write code in a more functional way. That being said, it was picked out because it can throw a few different exceptions. Without further ado, let's take a look at the code:

try {
    listOfNames.set(3, "John Carmack");
} catch(Exception e) {
    throw new RuntimeException("Could not insert element!");
}

As we can see we try to set the value of index 3 in our list of names to be John Carmack. The list might have come from somewhere else, or we might have created it ourselves earlier in the code, but that is not important right now. We run it, and suddenly we get an error:

java.lang.RuntimeException: Could not insert element!
	at net.themkat.blog.exceptionarticle.SimpleTest.test(SimpleTest.java:23)

If this is all you see in a log file (maybe this runs in production for some reason? Who knows), then you might wonder what went wrong. You might be familiar with the Java standard library and guess the problem fast, but since the above code can be caused by several different problems you might have the wrong guess. Maybe you guessed some sort of index out of bounds issue? Then you will be wrong in this case. How could we have seen this faster without any debugging? By including the cause off course!

try {
    listOfNames.set(3, "John Carmack");
} catch(Exception e) {
    throw new RuntimeException("Could not insert element!", e);
}

How does this differ? Let's us pretend the exception happens again:

java.lang.RuntimeException: Could not insert element!
	at net.themkat.blog.exceptionarticle.SimpleTest.test(SimpleTest.java:23)
...
Caused by: java.lang.UnsupportedOperationException
	at java.util.Collections$UnmodifiableList.set(Collections.java:1313)
	at net.themkat.blog.exceptionarticle.SimpleTest.test(SimpleTest.java:21)
	... 65 more

Now we see the cause in our logs straight away! Seems like we created an unmodifiable list! With simply adding the cause to the new exception we have way more information in our logs! Just imagine this being a more advanced scenario like calling an external REST API! Several errors can happen there, and when including the exception cause like above we can quickly see if it is a contract error, misconfigured URL, endpoint down or something else.

This is probably a simple example, but I think it proves the point.

NOTE: Usually we would not throw a RuntimeException, but one of its sub-exceptions (e.g, NoSuchElementException or your own) or some other subexception of Exception. In this case it is used as a simple example because we can easily set the cause, while some sub-exceptions can not.

Logging exceptions

Sometimes you may choose to log it instead of rethrowing. I prefer that approach if I don't want to throw a new exception, but just return a default value or similar. Your preference may be different. Why log the exception in this case, you may ask? We may want to know in our logs for debugging potential issues. What if the default value is returned each time? Then we might want to investigate and see what caused our original operation to fail (external endpoint down? Issue like in the previous section? Something else?).

Let's use a REST GET operation as an example; fetching a person object. In this case we might just return null as the default value if we can't get a Person object from the API.

try {
    return restTemplate.getForObject(url, Person.class);
} catch (RestClientException e) {
    log.error("Could not communicate with external api, {}", e);
    return null;
}

If the REST operation fails here, null will be returned, and we will have a new log entry (including timestamp) with the message and exception. The exception information will then be available for debugging.

For the more experienced developers, this was probably not that interesting. The reason I decided to write it, is because I see a lot of people forgetting about this part of exception handling. It can be easy to forget. Have you ever forgotten to include the cause, and it caused (pun not intended) you pain later on? Or do you have any other thoughts? Feel free to share in the comments :)




Other posts that might interest you: