70

I'm doing a lot of our validation with Hibernate and Spring Annotations like so:

public class Account {
    @NotEmpty(groups = {Step1.class, Step2.class})
    private String name;

    @NotNull(groups = {Step2.class})
    private Long accountNumber;

    public interface Step1{}
    public interface Step2{}
}

And then in the controller it's called in the arguments:

public String saveAccount(@ModelAttribute @Validated({Account.Step1.class}) Account account, BindingResult result) {
   //some more code and stuff here
   return "";
}

But I would like to decide the group used based on some logic in the controller method. Is there a way to call validation manually? Something like result = account.validate(Account.Step1.class)?

I am aware of creating your own Validator class, but that's something I want to avoid, I would prefer to just use the annotations on the class variables themselves.

Ross Hettel
  • 1,039
  • 1
  • 9
  • 19

7 Answers7

69

Spring provides LocalValidatorFactoryBean, which implements the Spring SmartValidator interface as well as the Java Bean Validation Validator interface.

// org.springframework.validation.SmartValidator - implemented by LocalValidatorFactoryBean
@Autowired
SmartValidator validator;

public String saveAccount(@ModelAttribute Account account, BindingResult result) {
    // ... custom logic
    validator.validate(account, result, Account.Step1.class);
    if (result.hasErrors()) {
        // ... on binding or validation errors
    } else {
        // ... on no errors
    }
    return "";
}
M. Justin
  • 14,487
  • 7
  • 91
  • 130
Pavel Horal
  • 17,782
  • 3
  • 65
  • 89
  • 3
    Where do you get the `BindingResult` value? – Jorn Oct 26 '20 at 11:14
  • That is injected by Spring MVC. Well... I assumed in my answer that we are talking about MVC handler method, because `ModelAttribute` does not make sense in any other context. – Pavel Horal Oct 26 '20 at 15:42
  • It's a [`ValidatorFactory`](https://docs.oracle.com/javaee/7/api/javax/validation/ValidatorFactory.html) bean, not a validator [`FactoryBean`](https://docs.spring.io/spring-framework/docs/5.3.8/javadoc-api/org/springframework/beans/factory/FactoryBean.html). `FactoryBean` is a Spring thing, `ValidatorFactory` is a Java EE bean validation thing. – M. Justin Aug 23 '21 at 21:30
  • @M.Justin You are correct, I have deleted my old rant from the answer. – Pavel Horal Nov 04 '21 at 07:44
48

Here is a code sample from JSR 303 spec

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

Driver driver = new Driver();
driver.setAge(16);
Car porsche = new Car();
driver.setCar(porsche);


Set<ConstraintViolation<Driver>> violations = validator.validate( driver );

So yes, you can just get a validator instance from the validator factory and run the validation yourself, then check to see if there are violations or not. You can see in the javadoc for Validator that it will also accept an array of groups to validate against.

Obviously this uses JSR-303 validation directly instead of going through Spring validation, but I believe spring validation annotations will use JSR-303 if it's found in the classpath

digitaljoel
  • 26,265
  • 15
  • 89
  • 115
  • 8
    The thing is, that if you are using Spring MVC, you probably want `BindingResult` / `Errors` instead of `ConstraintViolation`s. So while your answer is correct, it is not a good fit for the question. – Pavel Horal Oct 04 '13 at 21:33
  • 1
    This might be useful for Spring Rest Controller. You can throw `new ConstraintViolationException(violations);` when `violations` is not empty. It will be exactly the same what Spring `@Validated` would have caused and can be handled by `@ControllerAdvice`. – Michał Maciej Gałuszka Jul 09 '19 at 13:56
  • 2
    I am a newbie, but this did not seem to work. It gives a HV000183 exception, unable to initialize 'javax.el.ExpressionFactory'. – cody.tv.weber Jul 26 '19 at 21:31
20

If you have everything correctly configured, you can do this:

import javax.validation.Validator;

@Autowired
Validator validator;

Then you can use it to validate you object:

var errors = validator.validate(obj);
Pavel Vlasov
  • 4,206
  • 6
  • 41
  • 54
Jaiwo99
  • 9,687
  • 3
  • 36
  • 53
10

This link gives pretty good examples of using validations in Spring apps. https://reflectoring.io/bean-validation-with-spring-boot/

I have found an example to run the validation programmitically in this article.

class MyValidatingService {

  void validatePerson(Person person) {
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    Validator validator = factory.getValidator();
    Set<ConstraintViolation<Person>> violations = validator.validate(person);
    if (!violations.isEmpty()) {
      throw new ConstraintViolationException(violations);
    }
  } 
}

It throws 500 status, so it is recommended to handle it with custom exception handler.

@ControllerAdvice(annotations = RestController.class)
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<CustomErrorResponse> constraintViolationException(HttpServletResponse response, Exception ex) throws IOException {
        CustomErrorResponse errorResponse = new CustomErrorResponse();
        errorResponse.setTimestamp(LocalDateTime.now());
        errorResponse.setStatus(HttpStatus.BAD_REQUEST.value());
        errorResponse.setError(HttpStatus.BAD_REQUEST.getReasonPhrase());
        errorResponse.setMessage(ex.getMessage());
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }
}

Second example is from https://www.mkyong.com/spring-boot/spring-rest-error-handling-example/

Update: Using validation is persistence layer is not recommended: https://twitter.com/odrotbohm/status/1055015506326052865

Sarvar Nishonboyev
  • 12,262
  • 10
  • 69
  • 70
6

Adding to answered by @digitaljoel, you can throw the ConstraintViolationException once you got the set of violations.

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<NotionalProviderPaymentDTO>> violations = validator.validate( notionalProviderPaymentDTO );

        if(!violations.isEmpty()) {
            throw new ConstraintViolationException(violations);
        }

You can create your own exception mapper which will handle ConstraintViolationException and send the errors messages to the client.

Prabjot Singh
  • 4,491
  • 8
  • 31
  • 51
1

And also:

@Autowired
@Qualifier("mvcValidator")
Validator validator;

...
violations = validator.validate(account);
supercobra
  • 15,810
  • 9
  • 45
  • 51
0
import javax.validation.Validator;
import javax.validation.ConstraintViolation;

public class{
    @Autowired
    private Validator validator;
       .
       .
    public void validateEmployee(Employee employee){

        Set<ConstraintViolation<Employee>> violations = validator.validate(employee);
        if(!violations.isEmpty()) {
            throw new ConstraintViolationException(violations);
        }
    }
}

Here, 'Employee' is a pojo class and 'employee' is it's object

Shibin Francis
  • 482
  • 6
  • 18