In the process of building my blog, I want to be able to let the user know if something went wrong. It can vary from the standard error 404, a custom message, to my own custom exceptions. I'll show you how to throw custom exceptions with Spring Boot, so you can do it yourself next time!
Handling Exceptions
First, I want to define what my exception response towards the client would look like. I'm doing this with a simple class called ExceptionResponse and some properties.
public class ExceptionResponse {
private Date timestamp;
private String message;
private String details;
public ExceptionResponse(Date timestamp, String message, String details) {
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
}
All the properties also have get methods, but I've left that out of the code snippet. Since I don't expect to be changing the property values, I haven't implemented any set methods.
ExceptionResponseHandler
With the class for ExceptionResponse done, I can now start with handling the thrown exceptions. For this, I've created the ExceptionResponseHandler that inherits the ResponseEntityExceptionHandler
class. It is also annotated with @ControllerAdvice
. This annotation allows me to create one class that handles all exceptions for every controller in my application. So, I can add all the exceptions to be handled in this class keeping it in one spot for maintainability.
So, first things first, I want to have one generic method for handling any exception that is thrown by my application. Whatever happens, I still want to notify the user.
@ControllerAdvice
@RestController
public class ExceptionResponseHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public final ResponseEntity<ExceptionResponse> handleAllExceptions(Exception ex, WebRequest req) {
ExceptionResponse exceptionResponse = new ExceptionResponse(
new Date(),
ex.getMessage(),
req.Description(false)
);
return new ResponseEntity<>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Now, the ExceptionResponseHandler handles any exception that the application throws. It returns the timestamp when the error occurred, the message from the exception, and the request they used. That should provide the client-side application enough information to handle the rest towards the user, without giving away too much details of the backend.
Custom Exceptions in Spring Boot
With the general setup done, it is finally time to get to the custom exceptions in Spring Boot. For that, I need to create an exception class to begin with. In my case, I want to notify that the blog post being posted already exists. However, I am fairly certain that the duplication of items won't apply to just blog posts. Therefore, I made it a little more generic: ResourceAlreadyExistsException.
The ResourceAlreadyExistsException
class is a relatively simple one. It extends the RuntimeException
class, and you can add as many parameters to it as you like. I've kept it concise like this.
public class ResourceAlreadyExistsException extends RuntimeException {
public ResourceAlreadyExistsException(String property, String value) {
super(String.format(
"Resource with property %s and value %s already exists." +
"Make sure to insert a unique value for %s",
property, value, property));
}
}
Whenever I need to check for a unique resource, I can tell the user which specific property has what value that causes the error. Furthermore, I notify the user what action must be taken to avoid the error.
Choosing The Error Code
For every exception, there is a suitable error code. However, in this case, I wasn't sure what to use. Naturally, I searched around and stumbled upon a question on StackOverFlow discussing this very example.
The accepted answer was the error 409 Conflict code. Using this code means that the request cannot be completed due to a conflict in the current state of the resource. However, when thinking about this meaning, I feel like this is not the correct error code in my case. The state of the resource itself isn't the issue.
Another option is using error 400 Back Request, but that doesn't feel right either. This code suggests that the server doesn't understand the request being made, and therefore cannot process it. That isn't the case here either, because the server perfectly understands the request.
A bit further down the post, I stumbled upon an interesting answer: using error 422 Unprocessable Entity. The following is said about this error code in WebDAV RFC 4918:
The 422 (Unprocessable Entity) status code means the server understands the content type of the request entity (hence a 415 (Unsupported Media Type) status code is inappropriate), and the syntax of the request entity is correct (thus a 400 (Bad Request) status code is inappropriate) but was unable to process the contained instructions.
The definition of this error code seems to fall in line with the situation I am facing. The server understands the request; it is a valid request, but is unable to process the entity. It cannot process the entity, because it is a duplicate.
Updating the ExceptionResponseHandler
Finally, I chose to use error 422 for my ResourceAlreadyExistsException
. Still, I need to hook up this error message to the ExceptionResponseHandler
. The extra method is very similar to the method I created earlier for handling all exceptions. In fact, you can easily copy-paste this method for all the exceptions you have. All you have to do, is changing the Exception class to your exception and change the HttpStatus.<code>
.
@ExceptionHandler(ResourceAlreadyExistsException.class)
public final ResponseEntity<ExceptionResponse> handleResourceAlreadyExistsException(
ResourceAlreadyExistsException ex, WebRequest req) {
ExceptionResponse exceptionResponse = new ExceptionResponse(
new Date(),
ex.getMessage(),
req.getDescription(false)
);
return new ResponseEntity<>(exceptionResponse, HttpStatus.UNPROCESSABLE_ENTITY);
Conclusion
Figuring out how to get the custom exceptions working in Spring Boot certainly was a fun one to uncover. I'm glad that this solution is reusable for multiple projects as well! Next up is figuring out how to write unit tests for the layers. Preferably, I want to write unit tests with mocks, so I can test the functionality without any dependencies.
Top comments (0)