5

I am trying to add @NotNull constraint into my Person object but I still can @POST a new Person with a null email. I am using Spring boot rest with MongoDB.

Entity class:

import javax.validation.constraints.NotNull;

public class Person {
    @Id 
    private String id;
    private String username;
    private String password;
    @NotNull // <-- Not working
    private String email;
    // getters & setters
}

Repository class:

@RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends MongoRepository<Person, String> {
}

Application class:

@SpringBootApplication
public class TalentPoolApplication {
    public static void main(String[] args) {
        SpringApplication.run(TalentPoolApplication.class, args);
    }
}

pom.xml

...
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.0.BUILD-SNAPSHOT</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-rest</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
...

When I @POST a new object via Postman like:

{
  "username": "deadpool",
  "email": null
}

I still get STATUS 201 created with this payload:

{
    "username": "deadpool",
    "password": null,
    "email": null
     ....
     ....
}
Romell
  • 497
  • 3
  • 13
oxyt
  • 1,816
  • 3
  • 23
  • 33
  • Where is the controller? Show its code please. – dambros Apr 15 '16 at 14:05
  • @dambros I didn't know controller needed in this case. I can do all HTTP operations like this. – oxyt Apr 15 '16 at 14:06
  • 1
    OK, so whatever you receive your params, mark with `@Valid` before the object – dambros Apr 15 '16 at 14:11
  • 1
    @dambros His repository has a `@RepositoryRestResource` annotation, you don't need a controller in that case. Spring will automatically create a controller for REST calls if you use that. – Jesper Apr 15 '16 at 14:12
  • @Jesper this is new to me, and magic o.O – dambros Apr 15 '16 at 14:13
  • @dambros Spring Data is already wonderful magic, and with this it makes it even easier to make a REST webservice backed by a DB. This annotation comes from [Spring Data REST](http://projects.spring.io/spring-data-rest/). – Jesper Apr 15 '16 at 14:15
  • @Jesper do you have any idea about my problem? :) – oxyt Apr 15 '16 at 14:49
  • @oxyt No, sorry, I don't. If you were using JPA and a relational database you'd use `@Column(nullable = false)` but I don't know how you would do that with MongoDB. – Jesper Apr 15 '16 at 18:09
  • @Jesper thank you anyway. I had no such problems with MySQL and I have zero experience with MongoDB... So, I hope someone can answer this :) – oxyt Apr 16 '16 at 13:08
  • `@javax.validation.constraints.Size(min = 1)` maybe gave you different result. If not working as expected then you need to do configure _hibernate-validator (or other)_ implementation for JSR-303. If is working as expected then `@NotNull` converted to empty string `""` some where between maybe. – SidMorad Apr 19 '16 at 15:55
  • 1
    @oxyt, so this works only for JPA based implementations as there is a validity check before the data is inserted/updated via JPA and it boils up cleanly. I think this makes a great feature request at the Web level for the Spring Data Hateoas team. I think you should open one at their github location and see if you get more feedback on this issue. – Biju Kunjummen Apr 21 '16 at 14:23
  • 1
    @oxyt, opened a github issue here - https://github.com/spring-projects/spring-hateoas/issues/446 – Biju Kunjummen Apr 21 '16 at 19:08
  • 1
    You have to enable [validation](http://stackoverflow.com/a/22583492/5873923). – Marc Tarin Apr 22 '16 at 21:38

4 Answers4

7

I had the same problem, but just enabling validation didn't work for me, this did work with both JPA and MongoDb to save anyone else spending ages on this. Not only does this get validation working but I get a nice restful 400 error rather than the default 500.

Had to add this to my build.gradle dependencies

    compile('org.hibernate:hibernate-validator:4.2.0.Final')

and this config class

@Configuration
public class CustomRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {


   @Bean
   public Validator validator() {
       return new LocalValidatorFactoryBean();
   }

   @Override
   public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
       validatingListener.addValidator("afterCreate", validator());
       validatingListener.addValidator("beforeCreate", validator());
       validatingListener.addValidator("afterSave", validator());
       validatingListener.addValidator("beforeSave", validator());
   }
}
Romell
  • 497
  • 3
  • 13
  • I keep getting this error when I add your Configuration: **java.lang.AbstractMethodError: org.hibernate.validator.engine.ValidatorFactoryImpl.close()V** Do you have any idea how to resolve this ? (that V at the end is not a typo) – Red fx Nov 14 '17 at 10:02
3

i found it better to make my own version of @NotNull annotation which validates empty string as well.

@Documented
@Constraint(validatedBy = NotEmptyValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotEmpty {


    String message() default "{validator.notEmpty}";

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

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

}

public class NotEmptyValidator implements ConstraintValidator<NotEmpty, Object> {

    @Override
    public void initialize(NotEmpty notEmpty) { }

    @Override
    public boolean isValid(Object obj, ConstraintValidatorContext cxt) {
        return obj != null && !obj.toString().trim().equals("");
    }

}
Sheikh Abdul Wahid
  • 2,623
  • 2
  • 25
  • 24
0

You can either use the following code for validating

@Configuration
@Import(value = MongoAutoConfiguration.class)
public class DatabaseConfiguration extends AbstractMongoConfiguration 
{

  @Resource
  private Mongo mongo;

  @Resource
  private MongoProperties mongoProperties;

  @Bean
  public ValidatingMongoEventListener validatingMongoEventListener() {
    return new ValidatingMongoEventListener(validator());
  }

  @Bean
  public LocalValidatorFactoryBean validator() {
    return new LocalValidatorFactoryBean();
  }

  @Override
  protected String getDatabaseName() {
    return mongoProperties.getDatabase();
  }

  @Override
  public Mongo mongo() throws Exception {
    return mongo;
  }

}
Red fx
  • 1,071
  • 2
  • 12
  • 26
srini
  • 1
0

Normally, the @RestRepository will resolve into a controller than handles validation by itself, except if you Override the default behavior or it by including some @HandleBeforeSave, @HandleBeforeCreate, ... into your code.

A solution is to remove the @HandleBeforeSave, @HandleBeforeCreate, ... and then spring will handle the validation again.

Or if you want to keep them, you can provide a handler for any object validation like this:

@Component
@RepositoryEventHandler
public class EntityRepositoryEventHandler {

    @Autowired
    private Validator validator;

    @HandleBeforeSave
    @HandleBeforeCreate
    public void validate(Object o) {
        Set<ConstraintViolation<Object>> violations = this.validator.validate(o);

        if (!violations.isEmpty()) {
            ConstraintViolation<Object> violation = violations.iterator().next();

            // do whatever your want here as you got a constraint violation !

            throw new RuntimeException();
        }
    }
}
Mssm
  • 717
  • 11
  • 29