Presenting Exceptions to Users
Tags: bestpractices, java, cleancode, exceptions
Published: 2021-01-11 09:46:46

Many applications have to present the errors that happen in the application to their users in a nice human-readable form. This article covers some widely used best practices on this topic.


Before we even start, let’s make it clear:</p>


> **NEVER expose stack traces in your API responses**

It's not only ugly but dangerous from a security point of view. Based on the line numbers in that stack trace, an attacker might infer the libraries and versions that you're using, and attack you by exploiting their known weaknesses.


Then what to show our users?
## Purpose of Exception Message

The simplest idea is to display the exception message string to the users. For simple applications, this might work, but in medium-large applications, two issues arise:

First, you might want to unit-test that a certain exception was thrown. Asserting message strings will lead to brittle tests, especially when you start adding concatenating user input to those messages:

```
throw new IllegalArgumentException("Invalid username: " + username);
```

Secondly, one might argue that you're violating [Model-View-Controller](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller), because you're mixing diverging concerns in the same code: formatting the user-visible message AND detecting the business error condition.

> **Best Practice**: Use the exception message to report any technical details about the error that might be useful for developers investigating the exception.

You can go as far as to catch an exception in a method `x()` and re-throw it back wrapped in another exception with a useful message that captures the key debug information from that method `x()`. For example you might include method arguments, any interesting id, current row index, and any other useful debug information. That's the **Catch-rethrow-with-debug-message Pattern**:

```
throw new RuntimeException("For id: " + keyDebugId, e);
```

You can even instantiate and throw back the same exception type you caught previously. I'm perfectly fine as long as you keep the original exception in the chain by passing it to the new exception constructor as its `cause`.

Using this pattern will include in your final exception stack trace all the useful debug information you need, allowing you to avoid breakpoint-debugging. 👍 I'll explain more hints on how to avoid breakpointing in a future blog post.

At the other extreme please don't write 30+ words phrases in your exception messages. They will just distract the reader: their brain will find itself staring endlessly at that nice human-friendly phrase in the middle of that difficult code. And honestly, the exception messages are the least important things to read when browsing unknown code. Keep in mind that the first thing a developer does when debugging an exception is to click through the stack trace. So keep it simple (KISS).

> **Best Practice** Keep your exception messages concise and meaningful. Just like comments.


To sum up, I will recommend that in most cases you keep the exception message for concise debug information for developers' eyes only. User error messages should be externalized in  `.properties` files so you can easily adjust, correct, and use UTF-8 accents without any pain.

But then how to distinguish between different error causes? In other words, what should be the *translation key* in that properties file?

## Different Exception Subtypes

It might be tempting to start creating multiple exception subtypes, one for each business error of our application, and then distinguish between errors based on the exception type. Although fun at first, this will rapidly lead to creating hundreds of exception classes in any typical real-world applications. Clearly, that's unreasonable.

> **Best Practice**: Only create a new exception type `E1` if you use need to `catch(E1)` and selectively handle that particular exception type, to work-around or recover from it.

But in most applications, you almost never work-around or recover from exceptions. Instead, you typically terminate the execution of the current use-case/request by allowing the exception to bubble up to the outer layers.

## Error Code Enum

The best solution to distinguish between your non-recoverable error conditions is using an error code, not the exception message, nor the exception type.

Should that error code be an `int`? If it were, we would need the *Exception Manual* at hand every time we walk through the code. Horrible scenario! But wait! 

Every time in Java the range of values is finite and pre-known, we should always consider using an `enum`. Let's give it a first try:

```java
public class MyException extends RuntimeException {
    public enum ErrorCode {
        GENERAL,
        BAD_CONFIG;
    }

    private final ErrorCode code;

    public MyException(ErrorCode code, Throwable cause) {
        super(cause);
        this.code = code;
    }

    public ErrorCode getCode() {
        return code;
    }
}
```

And then every time we encounter an issue due to bad configuration, we could do `throw new MyException(ErrorCode.BAD_CONFIG, e);`.


Unit tests are also more robust when using `enum` for Error Codes, compared to matching the String message:

```java
// junit 4.13 or 5:
MyException e = assertThrows(MyException.class, () -> method(...));
assertThat(ErrorCode.BAD_CONFIG, e.getErrorCode());
```

Okay, we've sorted how to distinguish between errors, and we've mentioned that the user exception messages should be externalized in a `.properties` files. But who does and when does this translation? Let's see...

## The Global Exception Handler

Let's write a GlobalExceptionHandler that catches our exception and translates it into a friendly message for our users. User are typically sending HTTP requests, so we'll assume a REST endpoint in a Spring Boot application, but all other major web frameworks today offer similar functionality.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0pew55ax1pwbkj145xn9.jpeg)

```java
@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    private final MessageSource messageSource;
 
    public GlobalExceptionHandler(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @ExceptionHandler(MyException.class)
    @ResponseStatus // by default returns 500 error code
    public String handleMyException(MyException exception, HttpServletRequest request) {
        String userMessage = messageSource.getMessage(exception.getCode().name(), null, request.getLocale());
        log.error(userMessage, exception);
        return userMessage;
    }
}
```
The framework will pass to this `@RestControllerAdvice` every `MyException` that slips from any REST endpoint (`@RequestMapping`, `@GetMapping`...).

At this point there's sometimes a debate: where should we translate the error code: on server-side (in Java) or on the browser (in JavaScript)?

Although it's tempting to throw the problem on the frontend, translating the error codes on the backend tends to localize better the impact of adding a new error code. Indeed, why should the frontend code change when you add or remove a new error?

Plus, you might even write code to check at application startup that all the enum error codes have a translation in the `.properties` files. That's why I will translate the error on the backend and send user-friendly messages in the HTTP responses.

> **My Preference**: translate the error codes on the backend

I used a Spring `MessageSource` to read the user message corresponding to the error code of the caught `MyException`. Based on the user Locale, the `MessageSource` determines what file to read from, eg: `messages_RO.properties` for *RO*, `messages_FR.properties` for *fr*, or default from `messages.properties` for any other language. 

I extracted the user Locale from the *Accept-Language* header in the HTTP Request. In many applications however, the user language is read from the user profile, instead of the incoming HTTP request.

In `src/main/resources/messages.properties` we store the translations:

```java
BAD_CONFIG=Incorrect application configuration
```

That's it! Free internationalization for our error messages.

There's a minor problem left though. What if we want to report some user input causing the error back to the UI? We need to add parameters to `MyException`. After adding other convenience constructors, here's the complete code of it:

```java
public class MyException extends RuntimeException {
    public enum ErrorCode {
        GENERAL,
        BAD_CONFIG;
    }

    private final ErrorCode code;
    private final Object[] params;

    // canonical constructor
    public MyException(String message, ErrorCode code, Throwable cause, Object... params) {
        super(message, cause);
        this.code = code;
        this.params = params;
    }
    // 6 more overloaded constructors for convenience

    public ErrorCode getCode() {
        return code;
    }
    public Object[] getParams() {
        return params;
    }
} 
```

This will allow us to use those parameters in the `messages*.properties` files:

```
BAD_CONFIG=Incorrect application configuration. User info: {0}
```

Oh, and we have to pass those arguments to the `MessageSource`when we ask for the translation:

```java
String userMessage = messageSource.getMessage(exception.getCode().name(), exception.getParams(), request.getLocale());
```

To see the entire app running, checkout [this commit](https://github.com/victorrentea/exceptions-guide/commit/7aa14bd5215077a415db4cffb05c3bebb0a7405b), start the `SpringBoot` class and navigate to [http://localhost:8080](http://localhost:8080).


## Reporting General Errors

When should we use the `GENERAL` error code?

When it comes to what error messages to display to users, many good developers are tempted to distinguish between **as many errors** they can detect. In other words, they will consider that their users need to be notified about each and every failure cause. Although putting yourself in the shoes of your users is usually a good practice, in this case, it's a trap.

You should generally prefer displaying an opaque general-purpose error message, like "Internal Server Error, please check the logs". Consider carefully whether the ~developer~ user can do anything about that error or not. If not, do not bother to distinguish that particular error cause, because if you do, you'll have to make sure you continue to detect **that** error cause from then on. It may be simpler today, but who knows in 3 years... It's a good example of unnecessary and unforeseen long-term maintenance costs.

> **Best Practice**: Avoid displaying the exact error cause to your users unless they can do something to fix it.

Honestly, if the application configuration is corrupted, can the user really fix that? Probably not. Therefore we should use `new MyException(ErrorCode.GENERAL)` or another convenience constructor overload `new MyException()` which does the exact same thing.

We can simplify it even more, and directly `throw new RuntimeException("debug message?"...)`. Sonar may not be very happy about it, but it's the simplest possible form.

But what if there's no additional debug message to add to the new exception? Then your code might look like this:

```java
} catch (SomeException e)
	throw new RuntimeException(e);
}
```

Note that I didn't log the exception before re-throwing. Doing that is actually an anti-pattern, as I will explain in a future blog post.

But look again at the code snippet above. Why in the world would do you catch that `SomeException`? If it were a runtime exception, why don't you let it fly-through? So `SomeException` must be a checked exception and you're doing that to convert it into a runtime, invisible one. If that's the case, then you have another widely used option: throw a `@SneakyThrows` annotation from Lombok on that method to effectively let the checked exception propagate invisibly down the call stack. You can read more about this technique in [my other blog post](https://victorrentea.ro/blog/hide-checked-exceptions-with-sneakythrows/).

One last thing: we need to enhance our Global Exception Handler to also catch any other `Exception` that slips from a `RestController` method, like the infamous `NullPointerException`. We need to add another method:

```java
@ExceptionHandler(Exception.class)
@ResponseStatus
public String handleAnyOtherException(Exception exception, HttpServletRequest request) {
    String userMessage = messageSource.getMessage(ErrorCode.GENERAL.name(), null, request.getLocale());
    log.error(userMessage, exception);
    return userMessage;
}
```

Note that we used a similar code to the previous `@ExceptionHandler` method to read the user error messages from the same `.properties` file(s). You can do a bit more polishing around, but these are the main points.


## Conclusions
- Use concise exception messages to convey debugging information for developers
- Catch-rethrow-with-debug-message to display more data in the stack trace
- Only define new exception types in case you selectively catch them
- Use an error code enum to translate errors to users
- If needed, add the incorrect user input in the exception parameters and template your error messages
- We implemented a Global Exception Handler in Spring to catch, log and translate all exceptions slipped from HTTP requests (+internationalized)