Exception handling in spring boot REST APIs
As we know spring boot community is growing and lot of legacy spring project is getting migrated towards spring boot, and always when we start and application we always search for better error handling ways, usually when we are java developer we tend to use try catch block to throw our custom exception. there are design patterns to handle exceptions in java.lets not go there we shall see how to handle exception in Spring boot rest API's and translate that to end user providing meaningful messages and appropriate HTTP status code.
So before jumping into java code lets know common HTTP use in REST API, commonly we use 200 series for successful message, 400 series for representing the client error, 500 series for server related issue.So when we are developing API's i tend use these HTTP codes
- 201 Post created
Usually i use when new entry is created in the Database. - 200 Success
For Any non error request. - 404 resource not found
If we have some URL in which id is not present we provide 404 - 400
This is generic error code which is use for validation or improper request - 401
When use is not valid or user is Unauthenticated we throw this error code - 403
this request used when the user is authentic and he does not have permission to access the request. - 500
Generic error message when there is an error in the code or server.
So now we shall create exception for all these error codes, in the example i will create exception for 401 error message to restrict the post length.
public class NotAuthorizedException extends RuntimeException {
public NotAuthorizedException(String message) {
super(message);
}
}
Long long ago i used make big mistake handling these exceptions in controller which i simple duplicated code and which reflected in bad readable code, Now We Shall include the configuration which intercepts all these exception and translated to actual error response and displayed to user, instead repeating all the exceptions.
@ControllerAdvice
public class ExceptionAdvice {
@Value("${display.stacktrace}")
boolean stackTrace;
@ExceptionHandler({NotAuthorizedException.class, UserNotFoundException.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ResponseBody
public com.drees.cognito.config.response.ErrorResponse<Object> processAuthorizationError(final Exception ex) {
List<StackTraceElement> ele = null;
if (stackTrace == true) {
ele = Arrays.asList(ex.getStackTrace());
}
final com.drees.cognito.config.response.ErrorResponse<Object> response = new com.drees.cognito.config.response.ErrorResponse<>(
ele, ex.getMessage());
return response;
}
@ExceptionHandler(UserRestrictedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
@ResponseBody
public ErrorResponse<List<StackTraceElement>> processForbiddenError(Exception ex) {
List<StackTraceElement> ele = null;
if (stackTrace == true) {
ele = Arrays.asList(ex.getStackTrace());
}
ErrorResponse<List<StackTraceElement>> response = new ErrorResponse<>(ele, ex.getMessage());
return response;
}
@ExceptionHandler({InvalidParameterException.class, RuntimeException.class, GenericJDBCException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ErrorResponse<List<StackTraceElement>> processValidationError(Exception ex) {
List<StackTraceElement> ele = null;
if (stackTrace == true) {
ele = Arrays.asList(ex.getStackTrace());
}
ErrorResponse<List<StackTraceElement>> response = new ErrorResponse<>(ele, ex.getMessage());
return response;
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public ErrorResponse<List<StackTraceElement>> processAllError(Exception ex) {
List<StackTraceElement> ele = null;
if (stackTrace == true) {
ele = Arrays.asList(ex.getStackTrace());
}
ErrorResponse<List<StackTraceElement>> response = new ErrorResponse<>(ele, ex.getMessage());
return response;
}
}
the class name could be anything by @ControllerAdvice
at beginning of the code is very much require, and @ExceptionHandler(Exception.class)
instruct the controller advice configuration class when any exception is available with mentioned class to translate it to response written in the function. also do not forgot to add display.stacktrace
in property file which enable and disable stack trace in response, it will be easier to debug during development. also i have attached my ErrorResponse DTO class, but you can implement yours accordingly.
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ErrorResponse<T> {
private ErrorDTO<T> error;
public ErrorResponse(T object, String message) {
error = new ErrorDTO<T>(object, message);
}
public ErrorDTO<T> getError() {
return error;
}
public void setError(ErrorDTO<T> error) {
this.error = error;
}
}
As these exceptions are run time exceptions we don't need to propagate till the controller just throw where ever you feel like throwing an error. either in your service or controller or even in interceptor