Mastering Spring Boot Validation: Creating and Using Custom Validation Annotations

RAJESHRAJESH
4 min read

Spring Boot is one of the most popular frameworks for building Java-based applications. One of its most powerful features is validation, which allows developers to ensure that the data being processed adheres to specific rules. While Spring Boot offers a set of built-in validation annotations (like @NotNull, @Size, @Email, etc.), there are times when you need more custom validation rules to meet your application's specific requirements.

1. Introduction to Validation in Spring Boot

Spring Boot uses the Bean Validation API (JSR-303 and JSR-380) to support annotation-based validation. The validation is mostly used to validate the fields of Java objects, often those passed in as request bodies in REST APIs.

For instance, you can use annotations like:

@NotNull
private String username;

@Email
private String email;

However, when built-in annotations are not enough, you may need to create your own custom validation logic.

2. Creating a Custom Validation Annotation

To create a custom validation annotation, we need three components:

  1. The Custom Annotation

  2. The Validator Class

  3. Applying the Annotation

2.1 Custom Annotation

Let's create a custom annotation @ValidPhoneNumber that checks if a phone number is valid (in our case, ensuring it starts with a country code and contains only digits).

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)  // 1. Link to the validator
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPhoneNumber {

    String message() default "Invalid phone number";  // Default message

    Class<?>[] groups() default {};  // Grouping constraints (optional)

    Class<? extends Payload>[] payload() default {};  // Additional data (optional)
}

This annotation tells Spring to use the PhoneNumberValidator class to validate the annotated field.

Now, let’s break down some important annotations we used here:

  • @Target: This annotation specifies where the custom annotation can be applied (e.g., fields, methods, parameters).

  • @Retention: This tells the JVM how long the annotation should be retained. RetentionPolicy.RUNTIME means that the annotation will be available at runtime, which is necessary for validation to work.

  • @Constraint: This links our custom annotation to a specific validator class (in this case, PhoneNumberValidator.class), which contains the actual validation logic.

2.2 Validator Class

Next, we need to implement the logic for validating the phone number. The validator class must implement the ConstraintValidator<T, V> interface, where T is the annotation type (our custom annotation) and V is the field type (usually String in our case).

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

public class PhoneNumberValidator implements ConstraintValidator<ValidPhoneNumber, String> {

    @Override
    public void initialize(ValidPhoneNumber constraintAnnotation) {
        // Initialization logic (optional)
    }

    @Override
    public boolean isValid(String phoneNumber, ConstraintValidatorContext context) {
        // Check if the phone number matches the desired pattern
        if (phoneNumber == null) {
            return true;  // Null values are handled by @NotNull or another annotation
        }
        return phoneNumber.matches("^\\+\\d{1,4}\\d{7,10}$");  // Example pattern for phone number
    }
}

In this PhoneNumberValidator class, we define the isValid() method that checks if the phone number matches a certain regex pattern. If the validation fails, the error message defined in the annotation ("Invalid phone number") will be returned.

2.3 Using the Annotation

Now that the custom annotation and validator are in place, we can use the @ValidPhoneNumber annotation on any field in a model class.

javaCopyimport javax.validation.constraints.NotNull;

public class User {

    @NotNull
    private String name;

    @ValidPhoneNumber
    private String phoneNumber;

    // Getters and setters
}

Now, the phoneNumber field will be validated using our custom logic when the validation is triggered.

4. Handling Validation Errors

To handle validation errors globally, you can use an exception handler in a class annotated with @ControllerAdvice.

Here’s an example of a global exception handler that catches MethodArgumentNotValidException:

6. Conclusion

By using custom validation annotations in Spring Boot, you can implement complex business logic validation that goes beyond the built-in annotations. This flexibility allows you to tailor your application's validation requirements to specific needs, improving both data integrity and user experience.

With the concepts of @Target, @Retention, and @Constraint explained, you should be well-equipped to create custom validation annotations and integrate them into your Spring Boot applications.

0
Subscribe to my newsletter

Read articles from RAJESH directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

RAJESH
RAJESH