Implement validation message for default size validation

Viewed 804

I have DTO which I want to restrict for size:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class CreateUserDTO {

    @NotNull
    @NotEmpty
    @Size(min=1, max=20, message = "'Login' value '${validatedValue}' must be between {min} and {max} characters long.")
    private String login;

    @NotNull
    @NotEmpty
    @Size(min=1, max=45, message = "'Email' value '${validatedValue}' must be between {min} and {max} characters long.")
    private String email;

    @NotNull
    @NotEmpty
    @Size(min=1, max=20, message = "'First name' value '${validatedValue}' must be between {min} and {max} characters long.")
    private String firstName;

    @NotNull
    @NotEmpty
    @Size(min=1, max=20, message = "'Last name' value '${validatedValue}' must be between {min} and {max} characters long.")
    private String lastName;
}

Rest controller:

    @PostMapping("/create")
    public ResponseEntity<?> create(@Valid @RequestBody CreateUserDTO dto) {

        .....

        return ok().build();
    }

Any idea why I get this huge error message:

{
    "errors": [
        {
            "status": 404,
            "code": "1001",
            "title": "Not found",
            "detail": "Validation failed for argument [0] in public org.springframework.http.ResponseEntity<?> org.engine.rest.UsersController.create(org.engine.dto.CreateUserDTO): [Field error in object 'createUserDTO' on field 'login': rejected value [56dtttttttttt444444444465st]; codes [Size.createUserDTO.login,Size.login,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [createUserDTO.login,login]; arguments []; default message [login],20,1]; default message ['Login' value '56dtttttttttt444444444465st' must be between 1 and 20 characters long.]] ",
            "extra": {
                "detail": "Validation failed for argument [0] in public org.springframework.http.ResponseEntity<?> org.engine.rest.UsersController.create(org.engine.dto.CreateUserDTO): [Field error in object 'createUserDTO' on field 'login': rejected value [56dtttttttttt444444444465st]; codes [Size.createUserDTO.login,Size.login,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [createUserDTO.login,login]; arguments []; default message [login],20,1]; default message ['Login' value '56dtttttttttt444444444465st' must be between 1 and 20 characters long.]] "
            }
        }
    ]
}
5 Answers

Looking at the linked repo in your comment you could just add a new exception handler to customize constraint related validation errors.

Right now all the undefined exceptions are routed to as defined here.

You can define a exception handler for constraint related errors in the EngineExceptionhandler class.

Something like

@ExceptionHandler(ConstraintViolationException.class)
ResponseEntity<ErrorResponseDTO> constraintValidationException(final ConstraintViolationException e) {
    List<ErrorRerponse> errors = new ArrayList<>();
    for (ConstraintViolation violation : e.getConstraintViolations()) {
        ErrorResponse error = new ErrorResponse();
        error.setTitle(violation.getPropertyPath().toString());
        error.setDetail(violation.getMessage());
        errors.add(error);
    }
    ErrorResponseDTO errorResponse = new ErrorResponseDTO();
    errorResponse.setErrors(errors);
    return new ResponseEntity<ErrorResponseDTO>(errorResponse, HttpStatus.BAD_REQUEST);
}

You can add more customization based on your needs.

Simply The error message says where the issue is

default message ['Login' value '56dtttttttttt444444444465st' must be between 1 and 20 characters long.]

Since the value, you passed to login in CreateUserDTO dto with the request having characters more than the max value you have defined(20). Since the validation is failed It throws this error.

You need to handle MethodArgumentNotValidException

A quick implementation would be like

@ExceptionHandler({ MethodArgumentNotValidException.class })
public ResponseEntity<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
    BindingResult bindingResult = ex.getBindingResult();
    for (FieldError fieldError : bindingResult.getFieldErrors()) {
        // Do something with fieldError.getDefaultMessage();
        // Which is equivalent to 'Login' value '${validatedValue}' must be between {min} and {max} characters long.
    }

    for (ObjectError objectError : bindingResult.getGlobalErrors()) {
        // Do something with objectError.getDefaultMessage();
    }

    return ResponseEntity.ok().build();
}

I'm using the following ResponseEntityExceptionHandler which works fine for me.

@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {


  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(
      MethodArgumentNotValidException ex,
      HttpHeaders headers,
      HttpStatus status,
      WebRequest request) {
    Map<String, Map<String, List<Map<String, String>>>> errors = new HashMap<>();
    ex.getBindingResult()
        .getAllErrors()
        .forEach(
            error -> {
              String fieldName = ((FieldError) error).getField();
              String errorMessage = error.getDefaultMessage();
              Map<String, String> errorFields = new HashMap<>();
              errorFields.put("field", fieldName);
              errorFields.put("message", errorMessage);
              Map<String, List<Map<String, String>>> errorMap = new HashMap<>();
              errorMap.put("errors", List.of(errorFields));
              errors.put(fieldName, errorMap);
            });

    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
  }
}

You need to handle the root exception and beautify it before showing to end user or sending to client. As in mentioned above answers, you can override handleMethodArgumentNotValid method from ResponseEntityExceptionHandler as I did below:

@RestControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                  HttpHeaders headers, HttpStatus status, WebRequest request) {

        StringBuilder sb = new StringBuilder();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            sb.append("[");
            sb.append(((FieldError) error).getField());
            sb.append(" ");
            sb.append(error.getDefaultMessage());
            sb.append("] ");
        });

    log.error("Method arg is invalid. " + sb.toString(), ex);

    return new ResponseEntity<>(sb.toString(), HttpStatus.BAD_REQUEST);
    }
}

log.error() prints the message below:

// here is the message I show to the end user
10:27:02 [ERROR] [ServiceExceptionHandler] 141: Method arg is invalid. [shareList size must be between '1' and '10'] 
// here is the stack trace you encountered
org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public
...
[Size.eyatTransferDTO.shareList,Size.shareList,Size.java.util.List,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [eyatTransferDTO.shareList,shareList]; arguments []; default message [shareList],10,1]; default message [size must be between '1' and '10']]
...

Related