Reputation: 12214
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?
Upvotes: 8
Views: 12004
Reputation: 12214
@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.
Upvotes: 8
Reputation: 17361
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");
}
}
Upvotes: 3