user3602426
user3602426

Reputation:

Can I explicitly call custom validator from service in Spring Boot?

I have a custom validator class that implements Validator, like this:

 public class MyCustomValidator implements Validator

I want to be able to call its validate() method from a Service. This is how this method looks:

@Override
public void validate(Object target, Errors errors) {
     // validation goes here
     MyClass request = (MyClass) target;
     if (request.getId() == null) {
         errors.reject("content.id", "Id is missing";
     }
}

I don't want to have this validator in my endpoint, because I need to fetch the object to be validated from the database and then call the validation on it, so I need to do it from my service.

Can you please guide me on how to achieve this?

Upvotes: 3

Views: 4689

Answers (3)

Simon B
Simon B

Reputation: 92

The Validator interface is, as far as i understand it, called as soon as a matching object (determined by the public boolean Validator.supports(Class clazz) method).

However, your goal seems to be to validate an object of MyClass only at a specific time, coming from your persistence layer to your service layer.

There are multiple ways to achieve this.

The first and most obvious one is to not extend any classes, but to use a custom component with some notion of a validation function:

@Component
public class CustomValidator{
    public void validate(MyClass target) throws ValidationException {
        // validation goes here
        if (target.getId() == null) {
             throw new ValidationException("Id is missing");
        }
    }
}

And inject/autowire it into your service object:

@Component
public class MyClassService{
    // will be injected in first instance of this component
    @Autowired
    private CustomValidator validator

    public MyClass get(MyClass target) {
        try {
             validator.validate(target);
             return dao.retrieve(target);
        } catch (ValidationException) {
             // handle validation error
        } catch (DataAccessException) {
             // handle dao exception
        }

    }
}

This has the benefit that you yourself can control the validation, and error handling. The negative side is the relatively high boilerplate.

However, if you want different Validators for different CRUD-Operations (or Service Methods), you may be interested in the Spring Validation Groups Feature.

First, you create a simple marker interface for each Operation you want to differ:

interface OnCreate {};
interface OnUpdate {};

Then, all you need to do is use the marker interfaces in the fields of your entity class, using the Bean Validation Annotations:

public class MyClass{
    @Null(groups = OnCreate.class)
    @NotNull(groups = OnUpdate.class)
    String id;
}

In order to use those groups in your Service Class, you will have to use the @Validated annotation.

@Validated
@Service
public class MyService {
    @Validated(OnCreate.class)
    void validateForCreate(@Valid InputWithGroups input){
      // do something
    }

    @Validated(OnUpdate.class)
    void validateForUpdate(@Valid InputWithGroups input){
      // do something
    }
}

Note that @Validated is applied to the service class as well as the methods. You can also set the group for the whole service, if you plan on using multiple services.

I for once mostly use the built-in Jakarta Bean Validation annotations in combination with marker interfaces, because of their ease of use and almost no boilerplate, while staying somewhat flexible and adjustable.

Upvotes: 0

Eklavya
Eklavya

Reputation: 18430

Use validation annotations in class but don't use @Valid on request body, then spring won't validate your class.

public class MyClass{

   @NotNull
   private Integer id;

   @NotBlank
   private String data;
}

Autowired Validator first

@Autowired
private final Validator validator;

Then for class validate using the validator conditionally when needed.

if(isValidate) {
    Set<ConstraintViolation<MyClass>> violations = validator.validate(myClassObj);
    if (!violations.isEmpty()) {
      throw new ConstraintViolationException(new HashSet<ConstraintViolation<?>>(violations));
    }

}

Upvotes: 2

Simon Martinelli
Simon Martinelli

Reputation: 36123

You could inject Validator and call validate

@Autowired
Validator validator;

And then call validate:

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

Upvotes: 0

Related Questions