javaspringspring-bootbean-validationjsr

Spring Boot JSR-303/349 configuration


In my Spring Boot 1.5.1 application I'm trying to configure support of JSR-303 / JSR-349 validation.

I have added a following annotations @NotNull @Size(min = 1) to my method:

@Service
@Transactional
public class DecisionDaoImpl extends BaseDao implements DecisionDao {

    @Override
    public Decision create(@NotNull @Size(min = 1) String name, String description, String url, String imageUrl, Decision parentDecision, Tenant tenant, User user) {
        ...
    }

}

I'm trying to invoke this method from my test, but it does not fail on the validation constraints.

This is my test and configs:

@SpringBootTest(classes = { TestConfig.class, Neo4jTestConfig.class })
@RunWith(SpringRunner.class)
@Transactional
public class TenantTest {

    @Test
    public void testCreateDecision() {
        User user1 = userService.createUser("test1", "test1", "test1@test.com", null, null);
        Tenant tenant1 = tenantDao.create("Tenant 1", "Tenant 1 description", false, user1);

        // the following line should fail on the validation constraint because name parameter is null but it doesn't
        final Decision rootDecision = decisionDao.create(null, "Root decision 1 description", null, tenant1, user1);

...


@Configuration
@ComponentScan("com.example")
@SpringBootApplication(exclude={Neo4jDataAutoConfiguration.class})
public class TestConfig {
}

What am I doing wrong and how to configure JSR-303 there ?

UPDATED

I have added

public Decision create(@Valid @NotNull @Size(min = 1) String name, String description, Decision parentDecision, Tenant tenant, User author) {

but it still doesn't work

I have added @Validated to my DecisionDaoImpl but it fails now with a following exception:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'decisionDaoImpl': Bean with name 'decisionDaoImpl' has been injected into other beans [criterionGroupDaoImpl,characteristicGroupDaoImpl,tenantDaoImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:585)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
    ... 43 common frames omitted

I have also added @Lazy annotation in a places where I'm autowiring my DecisionDao but right now my test fails with a following exception:

javax.validation.ConstraintDeclarationException: HV000151: A method overriding another method must not alter the parameter constraint configuration, but method public com.example.domain.model.entity.decision.Decision com.example.domain.dao.decision.DecisionDaoImpl.create(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.Long,java.lang.Long,com.example.domain.model.entity.user.User) changes the configuration of public abstract com.example.domain.model.entity.decision.Decision com.example.domain.dao.decision.DecisionDao.create(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.Long,java.lang.Long,com.example.domain.model.entity.user.User).
    at org.hibernate.validator.internal.metadata.aggregated.rule.OverridingMethodMustNotAlterParameterConstraints.apply(OverridingMethodMustNotAlterParameterConstraints.java:24)
    at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.assertCorrectnessOfConfiguration(ExecutableMetaData.java:456)

Solution

  • Move your validation to interface, as follows:

    import javax.validation.Valid;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Size;
    
    public interface DecisionDao {
    
         Decision create(@Valid @NotNull @Size(min = 1) String name,
                String description, String url, String imageUrl);
    }
    

    Annotate your DecisionDaoImpl with @Validated, as follows:

    import org.springframework.stereotype.Service;
    import org.springframework.validation.annotation.Validated;
    
    @Service
    @Validated
    public class DecisionDaoImpl extends BaseDao implements DecisionDao {
    
        @Override
        public Decision create(String name,
                String description, String url, String imageUrl) {
            System.out.println(name);
            return new Decision();
        }
    
    }
    

    Modify your test case to verify for javax.validation.ConstraintViolationException using assertj or ExpectedException, as follows:

    import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
    
    import javax.validation.ConstraintViolationException;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
    
    @ContextConfiguration(classes = { TenantTest.Config.class })
    @RunWith(SpringRunner.class)
    public class TenantTest {
    
        @Autowired
        private DecisionDao decisionDao;
    
        @Rule
        public ExpectedException expectedException = ExpectedException.none();
    
        @Test
        public void testCreateDecisionUsingAssertj() {
            assertThatExceptionOfType(ConstraintViolationException.class)
                    .isThrownBy(
                            () -> decisionDao.create(null,
                                    "Root decision 1 description", null, null));
        }
    
        @Test
        public void testCreateDecision() {
           expectedException.expect(ConstraintViolationException.class);
           decisionDao.create(null, "Root decision 1 description", null, null);
        }
    
        @Configuration
        public static class Config {
            @Bean
            public MethodValidationPostProcessor methodValidationPostProcessor() {
                return new MethodValidationPostProcessor();
            }
    
            @Bean
            public DecisionDao decisionDao() {
                return new DecisionDaoImpl();
            }
        }
    }
    

    Make sure you have hibernate-validator in your classpath along with @StanislavL answer:

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
    </dependency>
    

    And an optional dependency for org.assertj.core.api.Assertions.assertThatExceptionOfType, as:

    <dependency>
         <groupId>org.assertj</groupId>
         <artifactId>assertj-core</artifactId>
         <version>3.3.0</version>
         <scope>test</scope>
    </dependency>
    

    For sample example, you can refer arpitaggarwal/jsr-303