How to add validation to your Spring Entities

ALT TEXT

A student had a question about validating data at the domain level and so I thought it would share it with you.

Hi Dan,  First of all thanks a lot for this great course. It really helped me to get into Spring. But now I'm facing a problem and didn't find a solution yet. I want to use for my DTO classes annotations like @NotNull (javax.validation.constraints) or custom annotations. But both don't work within spring boot. Do you know a good way or practice to solve this? Or is it too expensive to make these annotations work? If yes, is there a spring alternative for such annotations that execute a custom validation like a license-plate for instance?  I hope this question isn't too off-topic to this course and perhaps also interesting for another member of this course. Best wishes, Daniel  

Before we get started I just want to thank Daniel for the question. If you want to follow along with this project you can grab the source code here. We are going to start a new project and select the Web, JPA & H2 dependencies. 

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-actuator-docs</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Validation

I am gong to create an entity called the city and the key here is to look at the state property. We are using an annotation on the state @NotNull. This says that we create a new city and try to save it that the state can't be null. 

package com.therealdanvega;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;

@Entity
@Data
public class City {

    @Id @GeneratedValue
    private Long id;
    public String name;
    @NotNull
    public String state;

    private City() {}

    public City(String name){
        this.name = name;
    }
}

I then create a Command Line Runner to insert a new record. I am intentionally not adding the state to this object. 

package com.therealdanvega;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class ValidationApplication {

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

   @Bean
   CommandLineRunner runner(CityRepository cityRepository){
       return args -> {
         cityRepository.save( new City("Cleveland") );
      };
   }
}

When we try and run this application you will see the following error. 

Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [com.therealdanvega.City] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
    ConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=state, rootBeanClass=class com.therealdanvega.City, messageTemplate='{javax.validation.constraints.NotNull.message}'}

This was so easy to do and the great thing is it doesn't stop there. If you want to add all kinds of validation to different properties you can. Check out the documentation to find a list of annotations you can add for validation. 

Custom Validation

Most of the time the annotations provided will get the job the done. There are times when you need some type of custom validation done. In these cases, we can create our own custom validator and it's really easy to do.  Say on our City object we wanted an annotation where we can make sure the state was equal to "OHIO". I know this is a silly example but I want to keep it simple. This is what our domain object would look like now. 

package com.therealdanvega;

import com.therealdanvega.validator.StateValidator;
import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;

@Entity
@Data
public class City {

    @Id @GeneratedValue
    private Long id;
    public String name;

    @NotNull
    @StateValidator( value = "OHIO" )
    public String state;

    private City() {}

    public City(String name){
        this.name = name;
    }

    public City(String name, String state){
        this.name = name;
        this.state = state;
    }
}

Now we create our own annotation & StateValidatorCheck constraint. 

package com.therealdanvega.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Constraint(validatedBy = StateValidatorCheck.class)
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface StateValidator {
    String message() default "{com.therealdanvega.state.message}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    String value() default "";
}
package com.therealdanvega.validator;

import com.therealdanvega.City;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class StateValidatorCheck implements ConstraintValidator<StateValidator, String> {

    private String state;

    @Override
    public void initialize(StateValidator constraint) {
        this.state = constraint.value();
    }

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if( s.equalsIgnoreCase( this.state ))
            return true;

        return false;
    }
}

Now if we try and create a city object with a state other than OHIO we will get an error. 

package com.therealdanvega;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class ValidationApplication {

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

   @Bean
   CommandLineRunner runner(CityRepository cityRepository){
       return args -> {
         cityRepository.save( new City("Cleveland", "Tennesee") );
      };
   }
}
Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [com.therealdanvega.City] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
    ConstraintViolationImpl{interpolatedMessage='{com.therealdanvega.state.message}', propertyPath=state, rootBeanClass=class com.therealdanvega.City, messageTemplate='{com.therealdanvega.state.message}'}

Conclusion 

As you can see its pretty easy to sprinkle in some validation in your Spring Boot applications. 

Question: What are the challenges you face in validating data?

Subscribe to my newsletter.

Sign up for my weekly newsletter and stay up to date with current blog posts.

Weekly Updates
I will send you an update each week to keep you filled in on what I have been up to.
No spam
You will not receive spam from me and I will not share your email address with anyone.