0
0
SpringbootHow-ToBeginner · 4 min read

How to Create Custom Validator in Spring Boot Easily

To create a custom validator in Spring Boot, define a new annotation interface with @Constraint and implement ConstraintValidator for validation logic. Then, apply this annotation on your model fields to enforce custom validation rules.
📐

Syntax

Creating a custom validator involves two main parts:

  • Annotation Interface: Defines the custom annotation with @Constraint and metadata.
  • Validator Class: Implements ConstraintValidator to hold the validation logic.

Use the annotation on fields you want to validate.

java
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = YourValidator.class)
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface YourConstraint {
    String message() default "Invalid value";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

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

public class YourValidator implements ConstraintValidator<YourConstraint, String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // validation logic here
        return true; // or false
    }
}
💻

Example

This example creates a custom validator @StartsWithA that checks if a string starts with the letter 'A'. It shows how to define the annotation, implement the validator, and use it on a model field.

java
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = StartsWithAValidator.class)
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface StartsWithA {
    String message() default "Must start with letter A";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

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

public class StartsWithAValidator implements ConstraintValidator<StartsWithA, String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) return true; // null handled by @NotNull if needed
        return value.startsWith("A");
    }
}

// Model class using the custom validator
import jakarta.validation.constraints.NotNull;

public class User {
    @NotNull
    @StartsWithA
    private String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

// Example usage in a Spring Boot service or controller
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import jakarta.validation.ConstraintViolation;
import java.util.Set;

public class ValidatorTest {
    public static void main(String[] args) {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        User user1 = new User("Alice");
        User user2 = new User("Bob");

        Set<ConstraintViolation<User>> violations1 = validator.validate(user1);
        Set<ConstraintViolation<User>> violations2 = validator.validate(user2);

        System.out.println("User1 violations: " + violations1.size());
        System.out.println("User2 violations: " + violations2.size());
        violations2.forEach(v -> System.out.println(v.getMessage()));
    }
}
Output
User1 violations: 0 User2 violations: 1 Must start with letter A
⚠️

Common Pitfalls

  • Forgetting to add @Constraint(validatedBy = ...) on the annotation interface causes Spring to ignore the validator.
  • Not implementing ConstraintValidator with correct generic types leads to runtime errors.
  • Returning true for null values in isValid is standard; use @NotNull separately if nulls are invalid.
  • Not registering the validator if using custom validation outside Spring Boot's automatic scanning.
java
/* Wrong: Missing @Constraint annotation */
public @interface MissingConstraint {
    String message() default "Error";
}

/* Right: Include @Constraint with validator class */
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Constraint(validatedBy = CorrectValidator.class)
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface CorrectConstraint {
    String message() default "Error";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
📊

Quick Reference

Summary tips for creating custom validators in Spring Boot:

  • Define annotation with @Constraint, @Target, and @Retention.
  • Implement ConstraintValidator with proper types.
  • Return true for null values in isValid unless @NotNull is used.
  • Use the annotation on fields to enforce validation.
  • Test validation with Validator from jakarta.validation.

Key Takeaways

Create a custom annotation with @Constraint and link it to a validator class.
Implement ConstraintValidator with validation logic in isValid method.
Use @NotNull separately to handle null checks, as isValid should return true for null.
Apply your custom annotation on model fields to enforce validation rules.
Test your validator using jakarta.validation.Validator to catch errors early.