Stephane
Stephane

Reputation: 12810

@Valid Hibernate validation in a Spring REST controller

I'm trying to use the Hibernate Validation in a Spring REST controller with the @Valid annotation.

But the validation is not happening.

Here is what the Maven build has to say:

java.lang.AssertionError: Status expected:<400> but was:<201>

I should get a bad request http status when trying to post a form with invalid fields values, but instead the form posts just fine and the resource is created.

My validation tests tries to post with a missing email value and a too short login name value:

@Test
public void testValidation() throws Exception {
    HttpHeaders httpHeaders = Common.createAuthenticationHeaders("stephane" + ":" + PASSWORD);

MvcResult resultPost = this.mockMvc.perform(
        post("/admin").headers(httpHeaders)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON)
        .content("{ \"firstname\" : \"" + admin0.getFirstname() + "\", \"lastname\" : \"" + admin0.getLastname() + "\", \"email\" : \"" + admin0.getEmail() + "\", \"login\" : \"" + admin0.getLogin() + "\", \"password\" : \"" + admin0.getPassword() + "\", \"passwordSalt\" : \"" + admin0.getPasswordSalt() + "\" }")
    ).andDo(print())
    .andExpect(status().isBadRequest())
    .andExpect(jsonPath("$.firstname").value(admin0.getFirstname()))
    .andExpect(jsonPath("$.lastname").value(admin0.getLastname()))
    .andExpect(jsonPath("$.email").value(""))
    .andExpect(jsonPath("$.login").value("short"))
    .andExpect(jsonPath("$.password").value(admin0.getPassword()))
    .andExpect(jsonPath("$.passwordSalt").value(admin0.getPasswordSalt()))
    .andExpect(header().string("Location", Matchers.containsString("/admin/")))
    .andReturn();

}

I also have an exception handling class:

@ControllerAdvice
public class AdminExceptionHandler {

    @ExceptionHandler(AdminNotFoundException.class)
    @ResponseBody
    public ResponseEntity<ErrorInfo> adminNotFoundException(HttpServletRequest request, AdminNotFoundException e) {
        String url = request.getRequestURL().toString();
        String errorMessage = localizeErrorMessage("error.admin.not.found", new Object[] { e.getAdminId() });
        return new ResponseEntity<ErrorInfo>(new ErrorInfo(url, errorMessage), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public ResponseEntity<ErrorFormInfo> methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
        String url = request.getRequestURL().toString();
        String errorMessage = localizeErrorMessage("error.admin.invalid.form.argument");
        ErrorFormInfo errorFormInfo = new ErrorFormInfo(url, errorMessage);
        BindingResult result = e.getBindingResult();
        List<FieldError> fieldErrors = result.getFieldErrors();
        errorFormInfo.getFieldErrors().addAll(populateFieldErrors(fieldErrors));
        return new ResponseEntity<ErrorFormInfo>(errorFormInfo, HttpStatus.BAD_REQUEST);
    }

    public List<ErrorFormField> populateFieldErrors(List<FieldError> fieldErrorList) {
        List<ErrorFormField> errorFormFields = new ArrayList<ErrorFormField>();
        StringBuilder errorMessage = new StringBuilder("");
        for (FieldError fieldError : fieldErrorList) {
            errorMessage.append(fieldError.getCode()).append(".");
            errorMessage.append(fieldError.getObjectName()).append(".");
            errorMessage.append(fieldError.getField());
            errorFormFields.add(new ErrorFormField(fieldError.getField(), localizeErrorMessage(errorMessage.toString())));
            errorMessage.delete(0, errorMessage.capacity());
        }
        return errorFormFields;
    }

    private String localizeErrorMessage(String errorCode) {
        return localizeErrorMessage(errorCode, null);
    }

    private String localizeErrorMessage(String errorCode, Object args[]) {
        Locale locale = LocaleContextHolder.getLocale();
        String errorMessage = messageSource.getMessage(errorCode, args, locale);
        return errorMessage;
    }

}

Note that the AdminNotFoundException is triggered fine by some other test.

But the MethodArgumentNotValidException is not triggered.

Here is the controller class:

@Controller
@ExposesResourceFor(Admin.class)
@RequestMapping("/admin")
public class AdminController {

    @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<Admin> add(@RequestBody @Valid Admin admin, UriComponentsBuilder builder) {
        AdminCreatedEvent adminCreatedEvent = adminService.add(new CreateAdminEvent(admin.toEventAdmin()));
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.setLocation(builder.path("/admin/{id}").buildAndExpand(adminCreatedEvent.getAdminId()).toUri());
        Admin createdAdmin = adminResourceAssembler.toResource(adminCreatedEvent.getEventAdmin());
        ResponseEntity<Admin> responseEntity = new ResponseEntity<Admin>(createdAdmin, responseHeaders, HttpStatus.CREATED);
        return responseEntity;
    }

}

The versions of the dependencies are 3.2.5.RELEASE for Spring and 5.1.1.Final for hibernate-validator and 3.0.0 for javax.el-api

The same issue occurs when running manually against a Tomcat 7 server and issuing a curl post request:

mvn clean install tomcat7:run -DskipTests

curl -H "Accept:application/json" --user joethebouncer:mignet http://localhost:8080/learnintouch-rest/admin -X POST -H "Content-Type: application/json" -H "Accept: application/json" -d "{ \"firstname\" : \"Stephane\", \"lastname\" : \"Eybert\", \"email\" : \"\", \"login\" : \"short\", \"password\" : \"mignet\", \"passwordSalt\" : \"7bc7bf5f94fef7c7106afe5c3a40a2\" }"

Any clue ?

Here is the whole source code in case someone feels like trying to run this Maven build: http://www.learnintouch.com/learnintouch-data.tar.gz http://www.learnintouch.com/learnintouch-rest.tar.gz

Kind Regards,

Stephane Eybert

Upvotes: 1

Views: 4516

Answers (2)

geoand
geoand

Reputation: 64079

I tried your code, followed the same steps you mention and the validation did occur correctly.

This is the response I got back using the exact same curl command as you posted is:

{"url":"http://localhost:8080/learnintouch-rest/admin","message":"The admin form arguments were invalid.","fieldErrors":[{"fieldName":"login","fieldEr ror":"Length.admin.login"},{"fieldName":"email","fieldError":"NotEmpty.admin.email"}]}

I would suggest that you try clearing you maven repository and build the project from scratch.

I should also mention that in your testValidation() integration test the admin0 object that you are using is actually valid so it makes sense that it passes the validation :)

Upvotes: 1

pkainulainen
pkainulainen

Reputation: 1456

Have you tried using Hibernate Validator 4.2.0.Final? Hibernate Validator 5 is the reference implementation of bean validation API 1.1 (JSR 349) which isn't the same specification than JSR 303.

I assume that Spring Framework 3.2 supports only JSR 303 because I couldn't find any information about the bean validation API 1.1 from its reference manual.

Upvotes: 2

Related Questions