1

I am currently writing some @SpringBootTest for bean validation in services.

@Data
@Document
public final class Supplier {

    @Id
    @NotEmpty
    private String supplierId;
    ...
    @NotEmpty
    private String hash;
    ....
}

Test

Annotated with:

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK)

and

 @Test
    void testValidation() {
        Supplier invalidSupplier = SupplierTestDataUtil.createSupplier("1234");
        invalidSupplier.setSupplierId(null);

        //works
        assertThrows(ConstraintViolationException.class, () -> supplierService.publish(invalidSupplier));

        //works
        assertThrows(ConstraintViolationException.class, () -> supplierService.persist(invalidSupplier));
        
        //works not
        assertThrows(ConstraintViolationException.class, () -> supplierService.saveAndPublish(invalidSupplier));

        //works
        assertThrows(ConstraintViolationException.class, () -> supplierService.delete(invalidSupplier));
    }

Service:

@Transactional
public Supplier saveAndPublish(@NotNull Supplier supplier) {
    supplier.setHash(messageDigester.digest(supplier));
    Supplier persisted = persist(supplier);
    publish(supplier);
    return persisted;
}

@Transactional
public Supplier persist(@Valid @NotNull Supplier supplier) {
    return repository.save(supplier);
}

Supplier at saveAndFlush must not be valid at this point because required hash will be generated and set inside that method. Nevertheless my expectation was that ConstraintViolationException will be also thrown because I also call persist and publish method and pass that invalid document.

My point is that you can bypass BeanValidation within the same class.

1 Answer 1

1

The ConstraintViolationException will be thrown by a Spring Validator which won't be used if you call persist "manually". If persist is called from a Spring context, the @Valid annotation will tell Spring to validate the object based on the class's validation constraints.

You could:

  1. Validate hash always "manually" (without the @NotEmpty annotation)
  2. Call supplier.setHash(messageDigester.digest(supplier)); before calling saveAndPublish and add the @Valid annotation to the Supplier argument of saveAndPublish
  3. Add a Validator instance as a field to your service and call it manually on the Supplier to be validated (in case of saveAndPublish after setting the hash, of course)
  4. Implement two different types of Supplier, e.g, AddSupplier (without hash being validated; used by saveAndPublish) and EditSupplier (with hash being validated; used by persist)

(this is probably an incomplete list)

Sign up to request clarification or add additional context in comments.

4 Comments

This really makes annotation based validation useless for me. I want to implement "safe" methods where input parameters are always validated and I don't want to care about if this method is called from inside or outside my class. So I think I need to validate manually by using an injected Validator. pity
Well, you do have safe methods if you use two different Supplier implementations with specific validation constraints for the relevant cases (4.). You could define a common interface, an abstract class or a non-abstract, non-final base class and implement or extend the different implementations based on that.
Yes - there are several solutions for this specific problem. I'm still unhappy with my finding that you can bypass validation inside the class if you use annotation based bean validation.
I think the best solution will be to trigger validation manually as you mentioned in 3. Still not happy with this mixed solution (annotation based and manual) but I think it is the best for this scenario.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.