rvit34
rvit34

Reputation: 2106

spring rest Handling empty request body (400 Bad Request)

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?

Upvotes: 20

Views: 93091

Answers (9)

CKey
CKey

Reputation: 317

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 :) )

Upvotes: 0

MrG
MrG

Reputation: 11

    @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;
    }
}

Upvotes: 0

rvit34
rvit34

Reputation: 2106

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
            }
        }

Upvotes: 41

Velmurugan A
Velmurugan A

Reputation: 446

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());
    }

Upvotes: 2

Sarvar Nishonboyev
Sarvar Nishonboyev

Reputation: 13080

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);
    }

Upvotes: 0

Vivek
Vivek

Reputation: 13238

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.

Upvotes: 2

Nguyen Minh Hien
Nguyen Minh Hien

Reputation: 483

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!");
    } 
}

Upvotes: 7

Michal W
Michal W

Reputation: 330

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.

Upvotes: 2

Kishore Bandi
Kishore Bandi

Reputation: 5701

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

Upvotes: 2

Related Questions