Spring Boot custom Validation example

Data validation is very important. It conforms to the expected format, type, range, and business rules, as well as identify and prevent errors, inconsistencies, and fraud. When we need to perform data validation that cannot be handled by the built-in validation annotations provided by Spring Framework, we can use custom validation to define our own rules and constraints. In this tutorial, I will show you how to implement custom Validation annotation in Spring Boot example (with Rest API).

More Practice:
Spring Boot Validation annotations
Spring Boot Login and Registration example
Spring Boot 3 Rest API CRUD example
Spring Boot @ControllerAdvice & @ExceptionHandler example
Spring Boot Unit Test for Rest Controller
– Documentation: Spring Boot Swagger 3 example
– Caching: Spring Boot Redis Cache example
Spring Boot + GraphQL example


Spring Boot custom Validation example Overview

We will implement 2 Spring Boot custom validation annotations:

  • @StrongPassword: check if string is 8 characters long and combination of uppercase letters, lowercase letters, numbers, special characters.
  • @PasswordMatching: check if the password and confirm password are the same.

Here are the responses if users send invalid request data:

– Custom Validation for Password:

spring-boot-custom-validation-example

– Custom Validation for Confirm Password:

spring-boot-custom-validation-example-confirm-password

Project Structure

spring-boot-custom-validation-example-project

Let me explain it briefly.

StrongPassword.java and PasswordMatching.java define annotation interfaces for custom validation rules.
StrongPasswordValidator.java and PasswordMatchingValidator.java define validator classes that implement the validation logic.
SignupRequest is the payload class (or DTO) that uses the custom validation annotations.
UserController is the Rest Controller that exports API endpoint for receiving SignupRequest payload.
ValidationExceptionHandler handles error when the validation fails.

Setup Spring Boot project with Custom Validation

To use custom Validation annotation in Spring Boot, you need to add the spring-boot-starter-validation dependency to your Maven project’s pom.xml file:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Or Gradle project with build.gradle file:

implementation 'org.springframework.boot:spring-boot-starter-validation:3.0.6'

Create Custom Validation Annotation interface

In package validation, let’s create 2 new annotation interfaces that define custom validation rules using @Constraint and @Target annotations.

validation/StrongPassword.java

package com.bezkoder.spring.validation.custom.validation;

import java.lang.annotation.*;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

@Constraint(validatedBy = StrongPasswordValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface StrongPassword {
  String message() default "Must be 8 characters long and combination of uppercase letters, lowercase letters, numbers, special characters.";

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

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

validation/PasswordMatching.java

package com.bezkoder.spring.validation.custom.validation;

import java.lang.annotation.*;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

@Constraint(validatedBy = PasswordMatchingValidator.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface PasswordMatching {
  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 {
    PasswordMatching[] value();
  }
}

– The field message is for error message.
– The @Constraint annotation defines the actual validator implementation: StrongPasswordValidator and PasswordMatchingValidator.
@Target({ ElementType.TYPE }) indicates that we will attach the validator to the whole class.
We have to define the password and the confirmPassword field to tell the validator which fields we want to compare.

Create Validator class

Next, also in validation package, we create the custom validator class that implements ConstraintValidator interface for the validation logic.

We’ll check if string contains at least one digit, one lowercase letter, one uppercase letter, one special character and 8 characters long.

validation/StrongPasswordValidator.java

package com.bezkoder.spring.validation.custom.validation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    // check if string contains at least one digit, one lowercase letter, one uppercase letter, one special character and 8 characters long
    return value.matches("^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!*()]).{8,}$");
  }
}

We use String matches() function with regular expression for a strong password:

  • ^: the start of the string
  • (?=.*\d): at least one digit
  • (?=.*[a-z]): at least one lowercase letter
  • (?=.*[A-Z]): at least one uppercase letter
  • (?=.*[@#$%^&+=!*()]): at least one special character
  • .{8,}: at least 8 characters long
  • $: the end of the string

We continue to create custom validator class for validating multiple fields together.

validation/PasswordMatchingValidator.java

package com.bezkoder.spring.validation.custom.validation;

import org.springframework.beans.BeanWrapperImpl;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class PasswordMatchingValidator implements ConstraintValidator<PasswordMatching, Object> {

  private String password;
  private String confirmPassword;

  @Override
  public void initialize(PasswordMatching matching) {
    this.password = matching.password();
    this.confirmPassword = matching.confirmPassword();
  }

  @Override
  public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
    Object passwordValue = new BeanWrapperImpl(value).getPropertyValue(password);
    Object confirmPasswordValue = new BeanWrapperImpl(value).getPropertyValue(confirmPassword);

    return (passwordValue != null) ? passwordValue.equals(confirmPasswordValue) : confirmPasswordValue == null;
  }
}

The validator can read password and confirmPassword in the initialize() method, then check them later on in the isValid().

Use the Custom Validation

We will use the @StrongPassword annotation on a field in SignupRequest class that needs to be validated, and @PasswordMatching annotation for the whole class.

payload/SignupRequest.java

package com.bezkoder.spring.validation.custom.payload;

import com.bezkoder.spring.validation.custom.validation.PasswordMatching;
import com.bezkoder.spring.validation.custom.validation.StrongPassword;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

@PasswordMatching(
    password = "password",
    confirmPassword = "confirmPassword",
    message = "Password and Confirm Password must be matched!"
)
public class SignupRequest {
  @NotBlank
  @Size(min = 3, max = 20)
  private String username;

  @NotBlank
  @Size(max = 50)
  @Email
  private String email;

  @StrongPassword
  private String password;

  private String confirmPassword;

  //getters and setters
}

Validate the Data

Now we need to add the @Valid annotation to the field or method parameter we want to validate.

controller/UserController.java

package com.bezkoder.spring.validation.custom.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import com.bezkoder.spring.validation.custom.payload.SignupRequest;

import jakarta.validation.Valid;

@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api/auth")
public class UserController {

  @PostMapping("/signup")
  public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) {
    return new ResponseEntity<>("User registered Successfully!", HttpStatus.OK);
  }
}

Handle Custom Validation Exception

When the validation fails, Spring will throw an exception of type MethodArgumentNotValidException and we need to handle it in ValidationExceptionHandler class.

exception/ValidationExceptionHandler.java

package com.bezkoder.spring.validation.custom.exception;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import jakarta.servlet.http.HttpServletRequest;

@ControllerAdvice
public class ValidationExceptionHandler {

  @ExceptionHandler(MethodArgumentNotValidException.class)
  public ResponseEntity<?> notValid(MethodArgumentNotValidException ex, HttpServletRequest request) {
    List<String> errors = new ArrayList<>();

    ex.getAllErrors().forEach(err -> errors.add(err.getDefaultMessage()));

    Map<String, List<String>> result = new HashMap<>();
    result.put("errors", errors);

    return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
  }
}

For more detail about the technique, please visit:
Spring Boot @ControllerAdvice & @ExceptionHandler example

Conclusion

Today we’ve known step by step to implement Custom Validation in Spring Boot example. We created two Custom Validation annotations successfully for validating one field (strong password) and multiple fields (password and confirm password).

Happy Learning! See you again.

Source Code

You can find the complete source code for this tutorial on Github.

Further Reading

Fullstack CRUD App:
Spring Boot Thymeleaf example
Vue + Spring Boot example
Angular 8 + Spring Boot example
Angular 10 + Spring Boot example
Angular 11 + Spring Boot example
Angular 12 + Spring Boot example
Angular 13 + Spring Boot example
Angular 14 + Spring Boot example
Angular 15 + Spring Boot example
Angular 16 + Spring Boot example
React + Spring Boot example

More Practice:
Spring Boot – Validate Request Body
Spring Boot Rest API example: CRUD Application
Spring Boot Login and Registration example
Spring Boot Rest XML example
Spring Boot Multipart File upload example
Spring Boot Pagination and Sorting example
– Documentation: Spring Boot + Swagger 3 example (with OpenAPI 3)
– Caching: Spring Boot Redis Cache example
Spring Boot + GraphQL example