Sprint Boot @Valid is not throwing the message as described

Viewed 2518

I am new to Spring Boot, and learning by doing it. I saw couple of videos and there they are showing to use @Valid keyword before the @RequestBody. But if i am doing so i am getting the error but not as expected.

I am hitting this URL with method = POST http://localhost:8080/users and i am getting this reponse. NO Error Message as defined.

{
    "timestamp": "2021-04-30T08:35:24.384+00:00",
    "status": 400,
    "error": "Bad Request",
    "message": "",
    "path": "/users"
}

sharing the controller file

User Controller.java

package xyz.fastview.testing.ui.controller;    
import javax.validation.Valid;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import xyz.fastview.testing.ui.mode.request.UserDetailRequestModel;
import xyz.fastview.testing.ui.model.response.UserRest;

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping()
    public String createUser(@Valid @RequestBody UserDetailRequestModel userAccount) {
        return "Working Fine...!";
    }
}

UserDetailsRequestMode.java

package xyz.fastview.testing.ui.mode.request;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class UserDetailRequestModel {
    @NotNull(message = "firstName cannot be null")
    private String firstName;
    
    @NotNull(message = "lastName cannot be null")
    private String lastName;
    
    @NotNull(message = "email cannot be null")
    @Email(message = "email should be a proper email address")
    private String email;
    
    @NotNull
    @Size(min = 8, max = 20, message = "Password length can be 8 to 20 characters")
    private String password;

    // getters, setters
}
4 Answers

Turn on custome messages for validator in your application.properties or application.yml. YAML for example:

server.error.include-message: always

Since version 2.3, Spring Boot hides the message field in the response to avoid leaking sensitive information.

Controller advice is better solution for handle web error messages.

You can try below code also you can enrich response model.

@ResponseBody
@ControllerAdvice
public class GlobalControllerAdvice {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return errors;
    }
}

You are sending the following POST json request to your controller:

{"lastName":"lastname","email": "ankit@gmail.com", "password": "12345678"}

There is the missing parameter field firstName you declared as a NotNull field in your UserDetailRequestModel class with the annotation @NotNull:

public class UserDetailRequestModel {
    @NotNull(message = "firstName cannot be null")
    private String firstName;
    ... other fields
}

Indeed following your class definition an example of one correct json is the following:

{"firstName":"firstname", "lastName":"lastname","email": "ankit@gmail.com", "password": "12345678"}

The absence of an error message is due to the fact you haven't used the BindingResult as a parameter like I'm using below in your rewritten method:

@PostMapping()
    public String createUser(@Valid @RequestBody UserDetailRequestModel userAccount,
            BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            System.out.println(bindingResult.getAllErrors());
        }
        return "Working Fine...!";
    }

The method getAllErrors get all errors, both global and field ones, I'm printing it a primitive way, but the message is quite explicative that firstName parameter cannot be null:

[Field error in object 'userDetailRequestModel' on field 'firstName': rejected value [null]; codes [NotNull.userDetailRequestModel.firstName,NotNull.firstName,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userDetailRequestModel.firstName,firstName]; arguments []; default message [firstName]]; default message [firstName cannot be null]]

Couple of things you need to perform to show the validation message as part of the REST API response.

  1. Remove the below dependency
<dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
</dependency>
  1. Also you need to define an Exception Handler to propagate the message to the consumer.
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(
  MethodArgumentNotValidException ex) {
    Map<String, String> errors = new HashMap<>();
    ex.getBindingResult().getAllErrors().forEach((error) -> {
        String fieldName = ((FieldError) error).getField();
        String errorMessage = error.getDefaultMessage();
        errors.put(fieldName, errorMessage);
    });
    return errors;
}

Please refer to the tutorial for further assistance: https://www.baeldung.com/spring-boot-bean-validation

Related