How to disable Spring boots default error handling? - spring

I already tried disabling the Default Error handling of Spring boot w/c throws
{
"timestamp": 1575346220347,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.web.client.HttpClientErrorException",
"message": "401 Unauthorized",
"path": "/auth/login" }
By adding the ff. Config.
#SpringBootApplication(exclude = ErrorMvcAutoConfiguration.class)
and
spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
But I'm getting a bunch of HTML formatted Response instead of the JSON response it should be getting from the server.

You can Use Controller Advice to make a global exception handler. Inside the ControllerAdvice class, you can use #ExceptionHandler annotation to handle exceptions. Here is a good article about ControllerAdvice. https://medium.com/#jovannypcg/understanding-springs-controlleradvice-cd96a364033f

I was not able to disable SpringBoots automatic handling of Error responses however I was able to get the proper JSON Error Response by wrapping my Rest Template request in a try catch and using a library in the rest template as it turns out there is a bug in Rest Template that wouldn't allow me to retrieve the Response body.
From
private final RestTemplate restTemplate = new RestTemplate();
To
private final RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
Try-Catch Wrapping
ResponseEntity resp = null;
try{
resp = restTemplate.postForEntity(hostUrl+loginUrl, request,Object.class);
}catch(HttpClientErrorException e) {
ErrorDto result = new ObjectMapper().readValue(e.getResponseBodyAsString(), ErrorDto.class);
return new ResponseEntity<>(result, e.getStatusCode());
}
ErrorDto.java
#JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDto {
#JsonProperty("Message")
private String message;
#JsonProperty("Reason")
private String reason;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
}

Related

What is the proper way to control Required request body is missing Exception throwing?

I'm developing an API Service using Spring Boot with Maven. The problem is I want to control the Required request body is missing exception that is thrown to the client.
For example, I provide a API with POST method to the client. When the client call the API without Body. The Spring Boot will throw error in the body response like this,
{
"timestamp": "2021-09-14T18:05:47.992+00:00",
"status": 400,
"error": "Bad Request",
"trace": "org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<java.lang.Object>....
...
...
}
This will display the Controller name and line of code to the client. How can I just return some object to the client and like this,
{
"message": "Required request body is missing"
}
Thank you for every helps.
What you are looking for is a custom exception handler implementation. You need to override the following method in your custom exception handler.
The code would look somewhat like this:
#ControllerAdvice
#RestController
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
final MyMessageDto myExMsgDTO = new MyMessageDto("Required request body is missing");
return new ResponseEntity(myExMsgDTO, headers, status);
}
}
Here, your MyMessageDto class can be a simple POJO like this:
public class MyMessageDto {
private String message;
public MyMessageDto(String message) {
super();
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
You can add more variables in the DTO class mentioned above to give more details in case of exception.

how to get access to the default spring error JSON

seems like by default Spring will return a message of:
{
"timestamp": "2019-01-17T16:12:45.977+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Error processing the request!",
"path": "/my-endpoint-with-exceptions"
}
currently the app is using #RestControllerAdvice with an #ExceptionHandler on each exception. The in each method it uses a ResponseEntity
#ExceptionHandler(GenericException.class)
public ResponseEntity<String> exceptionHandler(GenericException ex){
return new ResponseEntity<>(ex.getMessage,HttpStatus.BAD_REQUEST)
}
additionally seems like over time there have been any number of classes which do about the same time as the default which are used.
So would rather use the default Spring JSON however of course do not want to impact currently running code. So my question is for just the GenericException to return the default Spring JSON?
I did try to use ResponseStatusException which did return the JSON but for whatever reason would only return a INTERNAL_SERVER_ERROR (500) status even when setting the value in the argument.
You can define your own error response and return it from the exception handler method.
Something like this:
Model:
#Builder
public class ErrorResponse {
private int status;
private String error;
private String message;
private String path;
private long timestamp;
}
Handler:
#ExceptionHandler(GenericException.class)
public ResponseEntity<ErrorResponse> exceptionHandler(GenericException ex){
ErrorResponse errorResponse = ErrorResponse.builder()
.message(ex.getMessage())
.status(HttpStatus.BAD_REQUEST.value())
.error(HttpStatus.BAD_REQUEST.getReasonPhrase())
.build();
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}

How to handle exceptions thrown in the service layer?

I'm working on a spring-boot application. I tried handling exceptions .But i guess there is something wrong about how I'm doing it because it always throws internal server error 500.
I tried setting up custom exception classes and also used response status codes with #ResponseStatus. But regardless of what the exception is it throws an internal server error only.
I'm using intellij and the message i've given in the exception is printed there but the response body is empty.This i guess must be because it is throwing an internal server error.
Controller class
#RequestMapping(value = "/attendance",method = RequestMethod.POST)
public ResponseEntity<?> enterAttendance(#RequestBody ViewDTO viewDTO) throws CustomException{
return new ResponseEntity<>(tempResultServices.handleAttendance(viewDTO),HttpStatus.OK);
}
}
Service layer
#Override
public TempResult handleAttendance(ViewDTO viewDTO) throws CustomException {
TempIdentity tempIdentity=new TempIdentity();
tempIdentity.setRegistrationNo(viewDTO.getRegistrationNo());
tempIdentity.setCourseId(viewDTO.getCourseId());
tempIdentity.setYear(viewDTO.getYear());
tempIdentity.setSemester(viewDTO.getSemester());
User user=userService.findByUserId(viewDTO.getUserId());
tempIdentity.setUser(user);
if(!viewDTO.isAttendance()){
TempResult tempResultUser =new TempResult(tempIdentity,viewDTO.isAttendance(),0);
ResultIdentity resultIdentity=new ResultIdentity(tempIdentity.getRegistrationNo(),tempIdentity.getCourseId(),tempIdentity.getYear(),tempIdentity.getSemester());
Result result=new Result(resultIdentity,0,"E*");
AttendanceDraft attendanceDraft=atteDraftService.findDraft(viewDTO.getRegistrationNo(),viewDTO.getCourseId(),viewDTO.getYear(),viewDTO.getSemester(),viewDTO.getUserId());
if(attendanceDraft!=null){
attendanceDraft.setStatus(true);
atteDraftService.save(attendanceDraft);
//atteDraftService.delete(attendanceDraft);
tempResultRepository.save(tempResultUser);
resultRepository.save(result);
return tempResultUser;
}
else{
throw new CustomException("No draft available");
}
}
else{
TempResult tempResultUser =new TempResult(tempIdentity,viewDTO.isAttendance());
AttendanceDraft attendanceDraft=atteDraftService.findDraft(viewDTO.getRegistrationNo(),viewDTO.getCourseId(),viewDTO.getYear(),viewDTO.getSemester(),viewDTO.getUserId());
if(attendanceDraft!=null){
attendanceDraft.setStatus(true);
atteDraftService.save(attendanceDraft);
//atteDraftService.delete(attendanceDraft);
tempResultRepository.save(tempResultUser);
return tempResultUser;
}
else{
throw new CustomException("No draft available");
}
}
}
The exception class
#ResponseStatus(code= HttpStatus.NOT_FOUND)
public class CustomException extends RuntimeException {
public CustomException(String message){
super(message);
}
}
The terminal in the intellij prints "No draft available ". But i want it not as an internal server error.
Can some one tell me how i should be handling these errors please?
I tried using the #RestControllerAdvice
#RestControllerAdvice
public class WebRestControllerAdvice {
#ExceptionHandler(CustomException.class)
public ResponseMsg handleNotFoundException(CustomException ex) {
ResponseMsg responseMsg = new ResponseMsg(ex.getMessage());
return responseMsg;
}
}
And this is my response message class
public class ResponseMsg {
private String message;
//getters and setters
}
This is another simple request in the application
#RequestMapping(value = "/user/view",method = RequestMethod.POST)
public ResponseEntity<?> getUser(#RequestBody UserDTO userDTO) throws CustomException{
User user=userService.findByUsername(userDTO.getUsername());
if(user!=null){
return ResponseEntity.ok(user);
}
//
throw new CustomException("User not found");
}
But still the custom exception is not thrown. The response body is empty. but intellij says "user not found" and postman returns the status code 500.
Spring boot has a very convenient way to handle exceptions in any layer of your application which is defining a #ControllerAdvice bean. Then you can throw any type of exception in your code and it will be "captured" on this class.
After this you can handle and return whatever your app needs to return.
By the way, you can return your custom object and it will be parsed to json automatically.
Documentation: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/
Sample code:
#ControllerAdvice
public class ErrorHandler {
#ExceptionHandler(BadRequestException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public Object processValidationError(BadRequestException ex) {
//return whatever you need to return in your API
}
}

Change Spring Boots default JSON error response structure

I have an API built with Spring Boot. By default the default JSON structure when an error is thrown by Spring is;
{
"timestamp": 1477425179601,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/categoriess"
}
This structure is different to error responses returning myself in the API, so I'd like to change Spring to use the same structure as my own for consistency.
My error response are structured like this;
{
"errors": [
{
"code": 999404,
"message": "The resource you were looking for could not be found"
}
]
}
How would I go about doing this? I've tried using an Exception Handler, but I can't figure out the correct exception to set it up for. I'd like to also make sure that the Http status is still correctly returned as 404, or whatever the error is (500 etc).
I had another look at this and did manage to put something together that works for me.
#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);
Map<String, Object> error = new HashMap<>();
error.put("code", errorAttributes.get("status"));
error.put("message", errorAttributes.get("error"));
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("errors", error);
return errorResponse;
}
};
}
This returns the following JSON response along with whatever header/http status code spring was going to return.
{
"errors": {
"code": 404,
"message": "Not Found"
}
}
This seems to work great for errors generated by spring, while my own Exceptions I'm handling in Controllers or in a specific ControllerAdmin class with ExceptionHandlers.
A possible way to do something like this is to use the #ExceptionHandler annotation to create a handler method inside your controller.
#RestController
#RequestMapping(produces = APPLICATION_JSON_VALUE)
public class MyController {
#RequestMapping(value = "/find", method = GET)
public Object find() {
throw new UnsupportedOperationException("Not implemented yet!");
}
#ExceptionHandler
public ErrorListModel handleException(Exception exception) {
ExceptionModel exceptionModel = new ExceptionModel(1337, exception.getMessage());
ErrorListModel list = new ErrorListModel();
list.add(exceptionModel);
return list;
}
private class ErrorListModel {
private List<ExceptionModel> errors = new ArrayList<>();
public void add(ExceptionModel exception) {
errors.add(exception);
}
public List<ExceptionModel> getErrors() {
return errors;
}
}
private class ExceptionModel {
private int code;
private String message;
public ExceptionModel(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
}
The private classes ErrorListModel and ExceptionModel just help defining how the resulting JSON body should look, and I assume you already have your own, similar classes.
The find method just throws an exception for us to handle, which gets intercepted by the handleException method because it's annotated with #ExceptionHandler. In here, we create an ExceptionModel, populate it with information from the original exception, and add it to an ErrorListModel, which we then return.
This blog post from 2013 explains the features better than I ever could, and it also mentions an additional option, #ControllerAdvice. It basically allows you to re-use the exception handling in other controllers as well.

Empty Exception Body in Spring MVC Test

I am having trouble while trying to make MockMvc to include the exception message in the response body. I have a controller as follows:
#RequestMapping("/user/new")
public AbstractResponse create(#Valid NewUserParameters params, BindingResult bindingResult) {
if (bindingResult.hasErrors()) throw BadRequestException.of(bindingResult);
// ...
}
where BadRequestException looks sth like this:
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "bad request")
public class BadRequestException extends IllegalArgumentException {
public BadRequestException(String cause) { super(cause); }
public static BadRequestException of(BindingResult bindingResult) { /* ... */ }
}
And I run the following test against /user/new controller:
#Test
public void testUserNew() throws Exception {
getMockMvc().perform(post("/user/new")
.param("username", username)
.param("password", password))
.andDo(print())
.andExpect(status().isOk());
}
which prints the following output:
Resolved Exception:
Type = controller.exception.BadRequestException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 400
Error message = bad request
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Does anybody have an idea on why is Body missing in the print() output?
Edit: I am not using any custom exception handlers and the code works as expected when I run the server. That is, running the application and making the same request to the server returns back
{"timestamp":1423076185822,
"status":400,
"error":"Bad Request",
"exception":"controller.exception.BadRequestException",
"message":"binding failed for field(s): password, username, username",
"path":"/user/new"}
as expected. Hence, there is a problem with the MockMvc I suppose. It somehow misses to capture the message field of the exception, whereas the default exception handler of the regular application server works as expected.
After opening a ticket for the issue, I was told that the error message in the body is taken care of by Spring Boot which configures error mappings at the Servlet container level and since Spring MVC Test runs with a mock Servlet request/response, there is no such error mapping. Further, they recommended me to create at least one #WebIntegrationTest and stick to Spring MVC Test for my controller logic.
Eventually, I decided to go with my own custom exception handler and stick to MockMvc for the rest as before.
#ControllerAdvice
public class CustomExceptionHandler {
#ExceptionHandler(Throwable.class)
public #ResponseBody
ExceptionResponse handle(HttpServletResponse response, Throwable throwable) {
HttpStatus status = Optional
.ofNullable(AnnotationUtils.getAnnotation(throwable.getClass(), ResponseStatus.class))
.map(ResponseStatus::value)
.orElse(HttpStatus.INTERNAL_SERVER_ERROR);
response.setStatus(status.value());
return new ExceptionResponse(throwable.getMessage());
}
}
#Data
public class ExceptionResponse extends AbstractResponse {
private final long timestamp = System.currentTimeMillis();
private final String message;
#JsonCreator
public ExceptionResponse(String message) {
checkNotNull(message, "message == NULL");
this.message = message;
}
}
This likely means that you either didn't handle the exception or you've really left the body empty. To handle the exception either add an error handler in the controller
#ExceptionHandler
public #ResponseBody String handle(BadRequestException e) {
return "I'm the body";
}
or user the global error handler if you're on 3.2 or above
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler
public #ResponseBody String handleBadRequestException(BadRequestException ex) {
return "I'm the body";
}
}
with this the body will be populate, you should populate it with your error message
Updated solution:
If you don't want to do a full integration test but still want to make sure the message is as expected, you can still do the following:
String errorMessage = getMockMvc()
.perform(post("/user/new"))
...
.andReturn().getResolvedException().getMessage();
assertThat(errorMessage, is("This is the error message!");

Resources