Short messages for MethodArgumentNotValidException

93 views Asked by At

Got a simple DTO for users of my app where I use jakarta validators for the length of the password.

public class UserDto {

  @NonNull
  @NotBlank private String username;

  @JsonProperty(access = WRITE_ONLY)
  @NonNull
  @ToString.Exclude
  @Size(min = 8, max = 30, message = "Please provide a password between 8 and 30 characters long.")
  private String password;
}

I'm making sure that I use the @Valid annotation on the controller for registering users:

@PostMapping(value = "/register")
public ResponseEntity<UserDto> registerUser(@RequestBody @Valid UserDto user) {
  return new ResponseEntity<>(userDetailsService.save(user), HttpStatus.CREATED);
}

Now, if I try to register a user with a password that is too short, e.g the password "pass":

{
  "username": "memeuser",
  "password": "pass"
}

I get a 400 and a pretty verbose error message that reveals several API internals:

{
  "message": "Validation failed for argument [0] in public org.springframework.http.ResponseEntity<com.agilebank.model.user.UserDto> com.agilebank.controller.JwtAuthenticationController.registerUser(com.agilebank.model.user.UserDto): [Field error in object 'userDto' on field 'password': rejected value [pass]; codes [Size.userDto.password,Size.password,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userDto.password,password]; arguments []; default message [password],30,8]; default message [Please provide a password between 8 and 30 characters long.]] "
}

I am decorating the Exception that is thrown through the following class and method:

@RestControllerAdvice
public class ExceptionAdvice {

  @ResponseBody
  @ExceptionHandler({
    HttpMessageNotReadableException.class,
    InsufficientBalanceException.class,
    SameAccountException.class,
    InvalidTransactionCurrencyException.class,
    OneOfTwoCurrenciesMissingException.class,
    InvalidSortByFieldSpecifiedException.class,
    MethodArgumentNotValidException.class // <---- Here is the type that interests us
  })
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public ResponseEntity<ExceptionMessageContainer> badRequestStatusMessage(Exception exc) {
    return new ResponseEntity<>(new ExceptionMessageContainer(exc.getMessage()), HttpStatus.BAD_REQUEST);
  }
}

where ExceptionMessageContainer is a simple container for the exception message:

@AllArgsConstructor
public class ExceptionMessageContainer {
    private String message;
}

Looking at the official docs for this exception type I am not so sure there's a straightforward way to get just the message that I have inside the @Size constraint, other than processing the String that getMessage() returns for me, which is not a generalizable solution. Any ideas about how I might be able to get just the message that I provide at the level of the constraint? I could of course also just target instances of MethodArgumentNotValidException through a separate method inside ExceptionAdvice and have something like:

 @ResponseBody
 @ExceptionHandler({
    MethodArgumentNotValidException.class
  })
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public ResponseEntity<ExceptionMessageContainer> methodArgumentNotValidExceptionSpecificHandler(MethodArgumentNotValidException exc) {
    return new ResponseEntity<>(new ExceptionMessageContainer("Password should be between 8 and 30 characters long"), HttpStatus.BAD_REQUEST);
  }

but there are other cases where this exception type might be thrown, since I have other DTOs with different validator constraints which can throw this exception type. So this would not work well in general and would mislead the user.

0

There are 0 answers