Custom Validation in Spring: Creating a Custom Annotation for Allowed Values

Publié le 15 novembre 2023 à 18:23

In a Spring application, data validation is a crucial aspect of ensuring the integrity and correctness of user inputs.
While Spring provides a wide range of built-in validation annotations, like @NotNull(), @NotBlank() etc
there are cases where you may need to define custom validation logic.
In this article, we’ll explore how to create a custom validation annotation in Spring to enforce allowed values on a field.

The Scenario

Consider a scenario where you have a class representing some data,
and you want to restrict a specific field to only accept certain values.
In a Rest Context, using a DTO Object as a request.
To achieve this, we’ll create a custom validation annotation called AllowedValues.

Creating the Custom Annotation :

Let’s start by defining our custom annotation:

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

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

@Documented
@Constraint(validatedBy = AllowedValuesValidator.class)
@Target(FIELD)
@Retention(RUNTIME)
public @interface AllowedValues {
  String message() default "Invalid value. Allowed values are {allowedValues}";

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

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

  String[] allowedValues();
}

Here, @Constraint indicates that our annotation is used for validation,
and @Target specifies that it can be applied to fields.
The AllowedValues annotation includes an array of allowed values, which will be used for validation.

Creating the Validator

Next, we need to implement the validation logic in a validator class:

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.Objects;

public class AllowedValuesValidator implements ConstraintValidator<AllowedValues, String> {

  private String[] allowedValues;

  @Override
  public void initialize(AllowedValues constraintAnnotation) {
    this.allowedValues = constraintAnnotation.allowedValues();
  }

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    return Objects.isNull(value) || Arrays.asList(allowedValues).contains(value);
  }
}

The AllowedValuesValidator implements the ConstraintValidator interface,
where initialize is called during initialization, and isValid contains the actual validation logic.

Using the Custom Annotation

Now, let’s use our custom annotation in a class:

import jakarta.validation.constraints.NotNull;

public class Vehicle {

    @NotNull(message = "Type cannot be null")
    @AllowedValues(allowedValues = {"Car", "Bicycle", "Truck"})
    private String type;

}

In this example, @NotNull ensures the field is not null,
and @AllowedValues enforces that the field’s value is one of the specified allowed values.
Spring will take care of the error sent to the client which will be 400 bad request.

Conclusion

Creating custom validation annotations in Spring allows you to tailor the validation logic to your specific needs.
The AllowedValues example demonstrated here can be extended and customized based on your application’s requirements.

Ajouter un commentaire

Commentaires

Il n'y a pas encore de commentaire.