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()
andpayload()
: 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); } }