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.