user3528733
user3528733

Reputation: 1299

Spring validation on rest controller param doesn't work

I have controller handler like this:

@RequestMapping(method = POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Add to basket")
@ApiResponses(value = {@ApiResponse(code = 200, message = "Successfully add to basket")})
@PreAuthorize("@securityService.hasUserAccess()")
public ResponseEntity addToBasket(@RequestBody @ItemAlreadyExistInBasket ProductEntity product) {
    basketService.addToBasket(product);
    return new ResponseEntity(HttpStatus.OK);
}

The think is that annotation

@ItemAlreadyExistInBasket

is never triggered and I dont know why. This is my annotation with Constraint validation

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = ItemAlreadyExistInBasketConstraint.class)
public @interface ItemAlreadyExistInBasket {
    String message() default "Item already exist in basket";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

public class ItemAlreadyExistInBasketConstraint implements ConstraintValidator<ItemAlreadyExistInBasket, ProductEntity> {

    @Autowired
    private BasketRepository basketRepository;

    @Autowired
    private UserRepository userRepository;

    @Override
    public void initialize(ItemAlreadyExistInBasket constraintAnnotation) {

    }

    @Override
    public boolean isValid(ProductEntity productEntity, ConstraintValidatorContext context) {
        userRepository.findByLogin(UserUtil.getAuthenticatedUserName());
        return basketRepository.findByUserAndStatusAndProduct(userRepository.findByLogin(UserUtil.getAuthenticatedUserName()), Status.ACTIVE, productEntity).isEmpty();
    }
}

Any ideas ?

Upvotes: 3

Views: 2317

Answers (1)

martin
martin

Reputation: 1691

I also had to deal with these method validations recently, don't know if you already found the answer for this problem but I am still going to answer it. The following works with Spring 4.1.2 (possibly earlier versions also)

1. Create following bean (in a @Configuration class for instance):

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
    return new MethodValidationPostProcessor();
}

2. Annotate your @RestController with @Validated

@RestController
@Validated
[...]
public class YourController ...

3. Implement your validators.

Just like you posted in your question, you may implement validators using the JSR303 spec.

4. Handling validation errors

Somehow, Spring deals with the validation errors differently when using @Value and method validation. So if you want your method to return a 400 error instead of throwing a server exception, you also need to implement something similar to this method:

/**
 * Handle Constraint exceptions in order to return a bad request http status
 */
@ExceptionHandler(value = { ConstraintViolationException.class })
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleResourceNotFoundException(ConstraintViolationException e) {
    Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
    StringBuilder strBuilder = new StringBuilder();
    for (ConstraintViolation<?> violation : violations ) {
        strBuilder.append(violation.getMessage() + "\n");
    }
    return strBuilder.toString();
}

Strangely, I had to place this method in my RestController. I did test implementing it inside a @Configuration class, but it didn't work.

Important!

Both type of validations are possible with @Valid and @Validated

If you want both POJO validation and method parameter validation, use both @Validated annotation on class level and @Valid before a POJO on the method. When using @Valid annotation, everything declared inside this POJO (for instance @NotNull on a field) will be also validated. This was the use case I had to implement.

Hope this helps!

Upvotes: 6

Related Questions