I want to handle exceptions in my Rest spring boot application. I know that with @ControllerAdvice and ResponseEntity I can return a custom object that will represent my error, but what I want is to add a new field to the body of the exesting exception that's all.
I created a custom Exception that inherit RuntimeException with an extra attribute, a list of string :
@ResponseStatus(HttpStatus.CONFLICT)
public class CustomException extends RuntimeException {
private List<String> errors = new ArrayList<>();
public CustomException(List<String> errors) {
this.errors = errors;
}
public CustomException(String message) {
super(message);
}
public CustomException(String message, List<String> errors) {
super(message);
this.errors = errors;
}
public List<String> getErrors() {
return errors;
}
public void setErrors(List<String> errors) {
this.errors = errors;
}
}
In my controller I just throw this custom exception this way:
@GetMapping("/appointment")
public List<Appointment> getAppointments() {
List<String> errors = new ArrayList<>();
errors.add("Custom message");
throw new CustomException("This is my message", errors);
}
When I test my Rest endpoint with postman, it seems like that spring boot doesn't marshall my errors field, the response is :
{
"timestamp": "2017-06-05T18:19:03",
"status": 409,
"error": "Conflict",
"exception": "com.htech.bimaristan.utils.CustomException",
"message": "This is my message",
"path": "/api/agenda/appointment"
}
I can go for a custom object with @ControllerAdvice if I can get the "path" and "timestamp" fields from the exception but there's no getters for these two attributes.
Thank you.
Well! Here is the implementation of "path" and "timestamp" in DefaultErrorAttributes which you could do it in your custom implementation too:
Path:
String path = getAttribute(requestAttributes, "javax.servlet.error.request_uri");
if (path != null) {
errorAttributes.put("path", path);
}
Timestamp:
errorAttributes.put("timestamp", new Date());
The documentation on error customization in spring boot is here.
@Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
// customize here
return errorAttributes;
}
};
}
Or you could write a custom implementation:
@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
// customize here
return errorAttributes;
}
}
The ErrorAttributes
bean customizes the error response below:
{
"timestamp": 1413883870237,
"status": 500,
"error": "Internal Server Error",
"exception": "org.example.ServiceException",
"message": "somthing goes wrong",
"path": "/index"
}
The "exception"
attribute can be customized using the @ExceptionHandler
. A @ControlerAdvice
could be used to customize the exception generically across controllers. To customize at the Controller level, you could place them within the controller.
In your case:
@ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="Invalid Inputs")
@ExceptionHandler(CustomException.class)
private void errorHanlder() {
//Log exception
}
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
Throwable error = getError(requestAttributes);
if (error instanceof CustomException) {
errorAttributes.put("errorList", ((CustomException)error).getErrors());
}
return errorAttributes;
}
Previous answer really has it all there but somehow it took me a while to figure it out, so in summary basically the simplest way to accomplish this is to have a bean like this:
@Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
Throwable error = getError(requestAttributes);
if (error instanceof CustomExceptionthere) {
errorAttributes.put("errorList", ((CustomException)error).getErrors());
}
return errorAttributes;
}
};