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);
}
}
