Learnitweb

Creating a Custom Validator (Annotation) in Spring Boot

In Spring Boot applications, you can use custom validation annotations when the built-in validation annotations (like @NotNull, @Size, @Email, etc.) are not sufficient for your business logic. This tutorial explains how to create a custom annotation and validator step by step.

1. Use Case Example

Suppose you have a User Registration API and you want to validate that the username field does not contain any special characters. Since there is no built-in annotation for this specific rule, we will create our own custom validator.

2. Create Custom Annotation

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = NoSpecialCharactersValidator.class)
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface NoSpecialCharacters {

    String message() default "Field must not contain special characters";

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

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

Explanation:

  • @Constraint: Links this annotation to the custom validator class.
  • @Target: Specifies that this annotation can be used on fields.
  • @Retention: Annotation is available at runtime.
  • message(): Default validation message.
  • groups() and payload(): Required for compatibility with Bean Validation API.

3. Create Validator Class

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

public class NoSpecialCharactersValidator implements ConstraintValidator<NoSpecialCharacters, String> {

    @Override
    public void initialize(NoSpecialCharacters constraintAnnotation) {
        // Initialization code if needed
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true; // Let @NotNull handle null case if required
        }
        return value.matches("^[a-zA-Z0-9]*$");
    }
}

Explanation:

  • initialize(): Optional method to initialize any configuration.
  • isValid(): Contains the logic to validate the input string.
  • Regex ^[a-zA-Z0-9]*$ ensures only alphanumeric characters are allowed.

4. Use Custom Annotation in Model

import javax.validation.constraints.NotNull;

public class UserRequest {

    @NotNull(message = "Username cannot be null")
    @NoSpecialCharacters(message = "Username should not contain special characters")
    private String username;

    // getters and setters
}

5. Controller Example

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping("/register")
    public ResponseEntity<String> registerUser(@Valid @RequestBody UserRequest userRequest) {
        return ResponseEntity.ok("User registered successfully!");
    }
}

6. Exception Handling

For a cleaner error response, you can add a global exception handler:

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

import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error ->
                errors.put(error.getField(), error.getDefaultMessage()));
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }
}