Spring Boot Custom Validator
In previous example we have seen Spring Boot + Method Level Custom Annotation from here.
In this tutorial will demonstrate how to implement custom annotations at the Field and Class level (Spring Boot Custom Validator) in a Spring Boot application.
Spring Boot + Field and Class Level Custom Annotation Example
In this example, We will create Field Level and Class Level Custom Annotation (custom validator) and use it in our model to enforce the validation rules.
Now create Spring Boot application from Spring Initializr.
Project Structure
Maven Dependency
Add spring-boot-starter-web
for RestController and for validation starter.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.techgeeknext</groupId>
<artifactId>spring-boot-field-level-custom-annotation</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-field-level-custom-annotation</name>
<description>Spring Boot with Field Level Custom Validation Annotation
</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
The validation starter comes with Spring Boot spring-boot-starter-web
depnedency.
Create the annotation
Let us create the annotation in Java by using @interface
.
We may define our class that will validate our field using the @Constraint
annotation,
the message()
is the error message that will be displayed in the response using the message()
, and
the remaining
code is the most repetitive code of spring standards using the rest code.
Email Validation
package com.techgeeknext.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.TYPE_USE })
@Constraint(validatedBy = EmailValidator.class)
public @interface ValidEmail {
String message() default "Invalid email";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
ValidEmail
We have given our custom annotation name asValidEmail
.@Target
It specifies where the annotation should be applied. Because it is at the field level in our case, we passElementType.FIELD
as a parameter.@Retention
It specifies when to use this annotation, which in our case is at run time.
Phone Number Validation
package com.techgeeknext.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Password Validation
package com.techgeeknext.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = PasswordConfirmationValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PasswordConfirmation {
String password();
String confirmPassword();
String message() default "Passwords must match!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@interface List{
PasswordConfirmation[] value();
}
}
Create Validator
The validation class must implement the isValid
function and implement the ConstraintValidator
interface; it is in this method that we specified our validation criteria.
ConstraintValidator
is a
class that defines the logic for validating a constraint for a specific object.
package com.techgeeknext.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;
public class EmailValidator implements ConstraintValidator<ValidEmail, String> {
private String regex = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
+ "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
@Override
public boolean isValid(String email, ConstraintValidatorContext context) {
if (email == null || email.isEmpty())
return true;
Pattern pat = Pattern.compile(regex);
return pat.matcher(email).matches();
}
}
Password Validator
Check whether user has provided same value for password and confirmation password object.
package com.techgeeknext.validator;
import org.springframework.beans.BeanWrapperImpl;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PasswordConfirmationValidator implements ConstraintValidator<PasswordConfirmation, Object> {
private String password;
private String confirmPassword;
@Override
public void initialize(PasswordConfirmation constraintAnnotation) {
this.password = constraintAnnotation.password();
this.confirmPassword = constraintAnnotation.confirmPassword();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
Object passwordValue = new BeanWrapperImpl(value).getPropertyValue(password);
Object confirmPasswordValue = new BeanWrapperImpl(value).getPropertyValue(confirmPassword);
if (passwordValue != null) {
return passwordValue.equals(confirmPasswordValue);
} else {
return confirmPasswordValue == null;
}
}
}
Take a look at our suggested posts:
Phone Number Validator
Check for valid phone number.
package com.techgeeknext.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
@Override
public boolean isValid(String phoneNumber, ConstraintValidatorContext constraintValidatorContext) {
return phoneNumber != null &&
phoneNumber.matches("[0-9]+") &&
phoneNumber.length() > 8 &&
phoneNumber.length() < 14;
}
}
Applying Validation Annotation
Now we will apply annotation for password + confirmation password at class level, phone number and for email validation at field level.
package com.techgeeknext.entity;
import com.techgeeknext.validator.ValidEmail;
import com.techgeeknext.validator.PasswordConfirmation;
import com.techgeeknext.validator.PhoneNumber;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@PasswordConfirmation>(
password = "password",
confirmPassword = "confirmPassword",
message = "Password and confirmation password must be the same!"
)
public class User {
private String firstName;
private String lastName;
@PhoneNumber(message = "This phone number is not valid")
private String phoneNumber;
private String password;
private String confirmPassword;
@NotBlank
@ValidEmail
private String email;
}
Create RestController
Create controller to test our custom validation by passing user details to rest endpoint.
We must ensure that our Model class has been annotated with the @Valid
annotation.
package com.techgeeknext.controller;
import com.techgeeknext.entity.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import javax.validation.Valid;
@Controller
public class UserController {
@PostMapping("/add/user")
public ResponseEntity<String> validateObject(@RequestBody @Valid User user) {
return new ResponseEntity<>("Valid User!", HttpStatus.OK);
}
}
Test Custom Annotation
- Start the Spring Boot Application by running
spring-boot:run
or by running main class. - Valid User Details: Provide valid User details in rest endpoint http://localhost:8082/add/user.
- Phone Number Validation: Provide User details with incorrect phone number in rest endpoint http://localhost:8082/add/user.
- Password Validation: Provide User details with mismatch password and confirm password in rest endpoint http://localhost:8082/add/user.
- Email Validation: Provide User details with incorrect email id in rest endpoint http://localhost:8082/add/user.
Download Source Code
The full source code for this article can be found on below.Download it here - Spring Boot + Field Level Custom Annotation Example