How to return 400 HTTP error code when some property of a RequestBody parameter is null?

10,790

Solution 1

@Bart's answer was very useful to find my final solution:

public class UserLoginData
    implements Serializable {

    private static final long serialVersionUID = 1L;

    @NotNull
    @NotBlank
    private String username;

    @NotNull
    @NotBlank
    private String password;
    //... getter and setters
}

On my Controller I have:

public LoginResponse login(
        @RequestBody(required = true) @Valid UserLoginData loginData){
    //... login code
}

Until here is very similar, but it is clearer because the controller's method does not have the error validation. Instead of that, I used another class with the ControllerAdvice annotation

@ControllerAdvice
public class RestErrorHandler {

    private MessageSource messageSource;

    @Autowired
    public RestErrorHandler(@Qualifier("messageSource") MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ValidationError processValidationError(MethodArgumentNotValidException ex) {
        BindingResult result = ex.getBindingResult();
        List<FieldError> fieldErrors = result.getFieldErrors();

        return this.processFieldErrors(fieldErrors);
    }

    private ValidationError processFieldErrors(List<FieldError> fieldErrors) {
        ValidationError dto = new ValidationError();

        for (FieldError fieldError : fieldErrors) {
            String localizedErrorMessage = this.resolveLocalizedErrorMessage(fieldError);
            dto.getErrors().put(fieldError.getField(), localizedErrorMessage);
        }

        return dto;
    }

    private String resolveLocalizedErrorMessage(FieldError fieldError) {
        Locale currentLocale = LocaleContextHolder.getLocale();
        String localizedErrorMessage = this.messageSource.getMessage(fieldError, currentLocale);

        return localizedErrorMessage;
    }
}

Now my service is returning this:

{
  "errors":{
    "country":"country cannot be null"
  }
}

I hope it helps someone else.

To get this solution I also used what is written in this post.

Solution 2

If the password is missing it will not be set when the UserLoginData object is created. It will not check if the value is valid or anything. If you need to validate your login data use proper validation.

You could use the annotations in the hibernate validator package for declarative validation e.g.

public class UserLoginData
    implements Serializable {

    private static final long serialVersionUID = 1L;

    @NotNull
    @NotBlank
    private String username;

    @NotNull
    @NotBlank
    private String password;
    //... getter and setters
}

Your method could then be written as (note the @Valid annotation):

public LoginResponse login(
        @RequestBody(required = true) @Valid UserLoginData loginData, 
        BindingResult result, 
        HttpServletResponse response){

    if (result.hasErrors()) {
        // Validation problems!
        response.sendError(400, "Bad login data");
    }
}
Share:
10,790
Neuquino
Author by

Neuquino

SOreadytohelp just ask me what you want to know

Updated on June 24, 2022

Comments

  • Neuquino
    Neuquino almost 2 years

    I have the following example:

    This is the request body:

    public class UserLoginData
        implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        private String username;
        private String password;
        //... getter and setters
    }
    

    This is the Controller:

    @RequestMapping(value = {"/login"}, method = RequestMethod.POST)
    @ResponseBody
    public LoginResponse login(@RequestBody(required = true) UserLoginData loginData){
        //... some code
     }
    

    This is how I invoke the service:

    POST /login
    {"username":"neuquino"}
    

    I expect that Spring returns a HTTP 400 BAD REQUEST error, because password is missing. But instead of that, it returns a HTTP 500 INTERNAL SERVER error with the following stacktrace:

    java.lang.NullPointerException
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:948) ~[spring-webmvc-3.2.2.RELEASE.jar:3.2.2.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838) ~[spring-webmvc-3.2.2.RELEASE.jar:3.2.2.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:755)
        ...
    

    How can I specify to Spring that username and password are required fields in request body?