spring rest Handling empty request body (400 Bad Request)
Asked Answered
L

9

20

I am developing a RESTful app using Spring4. I want to handle the case when a POST request contains no body. I wrote the following custom exception handler:

    @ControllerAdvice
    public class MyRestExceptionHandler {
     
      @ExceptionHandler
      @ResponseStatus(HttpStatus.BAD_REQUEST)
      public ResponseEntity<MyErrorResponse> handleJsonMappingException(JsonMappingException ex) {
          MyErrorResponse errorResponse = new MyErrorResponse("request has empty body");
          return new ResponseEntity<MyErrorResponse>(errorResponse, HttpStatus.BAD_REQUEST);
      }   
      @ExceptionHandler(Throwable.class)
      public ResponseEntity<MyErrorResponse> handleDefaultException(Throwable ex) {
        MyErrorResponse errorResponse = new MyErrorResponse(ex);
        return new ResponseEntity<MyErrorResponse>(errorResponse, HttpStatus.BAD_REQUEST);
      }
    }
    
     @RestController
     public class ContactRestController{
        @RequestMapping(path="/contact", method=RequestMethod.POST)
        public void save(@RequestBody ContactDTO contactDto) {...}
     } 

When it receives a POST with no body, these methods aren't called. Instead, the client gets a response with 400 BAD REQUEST HTTP status and empty body. Does anybody know how to handle it?

Lonilonier answered 22/12, 2016 at 22:52 Comment(2)
Have you tried @ExceptionHandler(JsonMappingException.class)?Devest
Yeah. The same behavior.Lonilonier
L
40

I solved the issue (the custom exception handler must extend ResponseEntityExceptionHandler). My solution follows:

        @ControllerAdvice
        public class RestExceptionHandler extends ResponseEntityExceptionHandler {
    
            @Override
            protected ResponseEntity<Object> handleHttpMessageNotReadable(
                HttpMessageNotReadableException ex, HttpHeaders headers,
                HttpStatus status, WebRequest request) {
                // paste custom hadling here
            }
        }
Lonilonier answered 23/12, 2016 at 21:57 Comment(2)
will it invoke only for post methods ?Myrtlemyrvyn
When combined the solution from these 2 links: It worked for me exactly the way I wanted. Solution Links: 1) stackoverflow.com/a/60643988 2) stackoverflow.com/a/41308274Tuberous
O
7

In my case, I need to handle all requests that have invalid parameters. So I extend my class with ResponseEntityExceptionHandler and override the method handleMissingServletRequestParameter. You can find your own handlers defined inside the class ResponseEntityExceptionHandler

@ControllerAdvice 
public class YourExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Exception.class)
    public final ResponseEntity handleAllExceptions(Exception ex) {
        // Log and return
    }

    @Override
    public ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        // Do your code here
        return new ResponseEntity<>("YOUR REQUEST PARAMS NOT MATCH!");
    } 
}
Othella answered 14/10, 2019 at 9:15 Comment(0)
E
2

I faced a similar issue and it didn't work for me because the component-scanpackage provided didn't include the package where my @ControllerAdvice was provided.

My XML had :

<context:component-scan base-package="com.bandi.rest" />

My package had a typo com.bandi.test.spring.exception. After changing it to com.bandi.rest.spring.exception it started working.

@ControllerAdvice
public class SpringRestExceptionHandler {

    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public @ResponseBody ResponseEntity<ErrorResponse> handleNoMethodException(HttpServletRequest request,
            NoHandlerFoundException ex) {
        ErrorResponse errorResponse = new ErrorResponse(ex);
        errorResponse.setErrorMessage("resource not found with exception");
        return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Throwable.class)
    public @ResponseBody ResponseEntity<ErrorResponse> handleDefaultException(Throwable ex) {
        ErrorResponse errorResponse = new ErrorResponse(ex);
        errorResponse.setErrorMessage("request has empty body  or exception occured");
        return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.BAD_REQUEST);
    }
}

Also, if you need to handle scenario where your requested resource was not found (bad URL), then you'll have to add another configuration to your dispatcher servlet.

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>throwExceptionIfNoHandlerFound</param-name>
        <param-value>true</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
</servlet>

Complete Working code is available here

Enrollment answered 23/12, 2016 at 1:12 Comment(5)
I am using Spring Boot so i don't need to do additional xml configuration.Spring finds all annotated beans. I will check your example laterLonilonier
Probably you need to add annotation based config then. As mentioned here Also can you verify if your @ComponentScan annotation includes the ExceptionHandler's package?Enrollment
In general my ExeptionHandler is working. All errors exept that in the topic are handled. Even NoHadlerFound exeption. I am using this advice to enable it.Lonilonier
Eventually found a solution.Lonilonier
can you please post solution you found?Import
B
2

If You already have a class annotated with @ControllerAdvice and don't want to create new one, You could use this piece of code:

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<?> handleMissingRequestBody(Exception ex) {
    return handle(BAD_REQUEST, ex);
}

It should have the same behaviour as rvit34's solution.

Bosket answered 6/2, 2019 at 13:49 Comment(0)
I
2

In the controller class, I putted below method and it solved my issue. no need of controller advise or any other. Just overriding spring default exception with our user exception with body itself will solve the issue.

@ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = {MissingServletRequestParameterException.class})
    ApiError handleMethodArgumentNotValid(MissingServletRequestParameterException ex) {

        return new ApiError(ErrorCode.MISSING_REQUIRED_PARAMS, ex.getMessage());
    }
Ibbetson answered 10/3, 2021 at 10:52 Comment(0)
D
1

I have encountered the same issue, after spending lot of time debugging the issue I found the jackson is not properly deserializing the ErrorResponse object.

This was because I didn't added the getters and setters for the field defined in the ErrorResponse object. I was initializing the fields using constructor and there were no getters and setters defined for those fields.

SOLUTION:

So when I updated my ErrorResponse object from

import com.fasterxml.jackson.annotation.JsonRootName;
import java.util.List;

@JsonRootName("error")
public class ErrorResponse {

  private String message;
  private List<String> details;

  public ErrorResponse(String message, List<String> details) {
    this.message = message;
    this.details = details;
  }
}

to the following one with getters and setters

import com.fasterxml.jackson.annotation.JsonRootName;
import java.util.List;

@JsonRootName("error")
public class ErrorResponse {

  private String message;
  private List<String> details;

  public ErrorResponse(String message, List<String> details) {
    this.message = message;
    this.details = details;
  }

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }

  public List<String> getDetails() {
    return details;
  }

  public void setDetails(List<String> details) {
    this.details = details;
  }
}

Jackson is now deserializing the ErrorResponse properly and I'm getting the serialized body in the response.

Dryer answered 20/5, 2020 at 7:9 Comment(0)
N
0

override handleExceptionInternal method handles all the exceptions, so you don't need to override each handle methods:

    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, 
HttpStatus status, WebRequest request) {
        MyErrorResponse myErrorResponse = new MyErrorResponse();
        MyErrorResponse.setMessage(ex.getMessage());
        return new ResponseEntity<>(myErrorResponse, status);
    }
Nitrate answered 27/1, 2021 at 17:14 Comment(0)
H
0
    @ControllerAdvice
    @ResponseBody
    public class ControllerExceptionHandler {

    @ExceptionHandler(BadCredentialsException.class)
    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    public BaseResponseBean badCredentialsException(BadCredentialsException ex, WebRequest request) {
        BaseResponseBean responseBean = new BaseResponseBean();
        StatusBean status = new StatusBean();
        status.setEnErrorMessage(ex.getMessage());
        status.setCode(403);
        status.setSuccess(false);
        responseBean.setStatus(status);
        return responseBean;
    }
    @ExceptionHandler(HttpClientErrorException.BadRequest.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public BaseResponseBean badRequestException(HttpClientErrorException.BadRequest ex, WebRequest request) {
        BaseResponseBean responseBean = new BaseResponseBean();
        StatusBean status = new StatusBean();
        status.setEnErrorMessage(ex.getMessage());
        status.setCode(400);
        status.setSuccess(false);
        responseBean.setStatus(status);
        return responseBean;
    }
}
Humboldt answered 15/11, 2021 at 22:15 Comment(0)
P
0

hmm..., i think you don't need to write a custom error response class, if you just want to inform the caller that the request body is empty. i think you can rely on the default spring error handling, but with additional setting.

first, add the @Valid annotation to your method parameter:

 @RestController
 public class ContactRestController{
    @RequestMapping(path="/contact", method=RequestMethod.POST)
    public void save(@Valid @RequestBody ContactDTO contactDto) {...}
 }

(btw. for this you need to add the "spring-boot-starter-validation" dependency)

and add following in you application.yaml:

server:
  error:
    include-message: always

This results in something like

{
    ...,
    "status": 400,
    "error": "Bad Request",
    "message": "Required request body is missing: public org.springframework.http.ResponseEntity<...",
    ...
}

(fiy: it's said that sensitive information can be disclosured by doing this, so think about if it's okay :) )

Perishing answered 9/4 at 15:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.