Custom validator annotations let you check if data meets your own rules. This helps keep your app data clean and correct.
Custom validator annotation in Spring Boot
Start learning this pattern below
Jump into concepts and practice - no test required
import jakarta.validation.Constraint; import jakarta.validation.Payload; import java.lang.annotation.*; @Documented @Constraint(validatedBy = YourValidatorClass.class) @Target({ ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface YourAnnotation { String message() default "Validation failed message"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
The @Constraint annotation links your custom annotation to its validator class.
The message is the error shown if validation fails.
@NotEmptyString to check if a string is not empty.import jakarta.validation.Constraint; import jakarta.validation.Payload; import java.lang.annotation.*; @Documented @Constraint(validatedBy = NotEmptyStringValidator.class) @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface NotEmptyString { String message() default "String must not be empty"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; public class NotEmptyStringValidator implements ConstraintValidator<NotEmptyString, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { return value != null && !value.trim().isEmpty(); } }
This example creates a custom annotation @StartsWithA that checks if a string starts with 'A'. The Person class uses it on the name field. The main method validates two persons and prints violations.
import jakarta.validation.Constraint; import jakarta.validation.Payload; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import jakarta.validation.Validation; import jakarta.validation.Validator; import jakarta.validation.ValidatorFactory; import jakarta.validation.constraints.NotNull; import java.lang.annotation.*; import java.util.Set; import jakarta.validation.ConstraintViolation; @Documented @Constraint(validatedBy = StartsWithAValidator.class) @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @interface StartsWithA { String message() default "Must start with letter A"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } class StartsWithAValidator implements ConstraintValidator<StartsWithA, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { return value != null && value.startsWith("A"); } } class Person { @NotNull @StartsWithA String name; Person(String name) { this.name = name; } } public class CustomValidatorDemo { public static void main(String[] args) { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); Person p1 = new Person("Alice"); Person p2 = new Person("Bob"); Set<ConstraintViolation<Person>> violations1 = validator.validate(p1); Set<ConstraintViolation<Person>> violations2 = validator.validate(p2); System.out.println("Violations for p1: " + violations1.size()); System.out.println("Violations for p2: " + violations2.size()); for (var v : violations2) { System.out.println(v.getPropertyPath() + ": " + v.getMessage()); } } }
Always implement ConstraintValidator with your annotation and the type you want to validate.
Use @Target to specify where your annotation can be used (fields, methods, etc.).
Remember to add @Retention(RetentionPolicy.RUNTIME) so the annotation is available at runtime.
Custom validator annotations let you create your own rules for data checks.
You link the annotation to a validator class that contains the logic.
Use them to keep validation clean and reusable across your app.
Practice
custom validator annotation in Spring Boot?Solution
Step 1: Understand the role of custom validator annotations
They allow you to create your own rules for validating data beyond built-in checks.Step 2: Identify the main benefit
These annotations keep validation logic clean and reusable across different parts of your app.Final Answer:
To define your own validation rules reusable across your application -> Option AQuick Check:
Custom validator = reusable validation rules [OK]
- Thinking custom validators replace all built-in annotations
- Confusing validation with database or HTTP handling
- Assuming custom validators auto-generate code
Solution
Step 1: Recall the syntax for custom annotation interfaces
They use@interfacekeyword and define methods likemessage(),groups(), andpayload().Step 2: Check each option
@interface MyValidator { String message() default "Invalid"; Class[] groups() default {}; Class[] payload() default {}; } correctly uses@interfaceand includes required methods. Others either use wrong keywords or miss required parts.Final Answer:
@interface MyValidator { String message() default "Invalid"; Class[] groups() default {}; Class[] payload() default {}; } -> Option DQuick Check:
Custom annotation = @interface + standard methods [OK]
- Using class or interface instead of @interface
- Omitting groups() or payload() methods
- Adding methods unrelated to validation
public class AlphaValidator implements ConstraintValidator<Alpha, String> {
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && value.matches("^[a-zA-Z]+$");
}
}Solution
Step 1: Analyze the validation logic
The method checks if the string is not null and matches the regex "^[a-zA-Z]+$", which means only letters allowed.Step 2: Test the input "abc123" against the regex
Since "abc123" contains digits, it does not match the regex, so the method returns false.Final Answer:
Validation fails because the string contains digits -> Option CQuick Check:
Regex allows only letters, digits cause failure [OK]
- Assuming partial match passes validation
- Ignoring null check in code
- Thinking digits are allowed by regex
Solution
Step 1: Understand the role of isValid method
This method contains the validation logic and must return true only for valid inputs.Step 2: Identify why validation always passes
IfisValidalways returns true, invalid data will pass unchecked.Final Answer:
The isValid method always returns true regardless of input -> Option BQuick Check:
isValid controls validation result; always true means always pass [OK]
- Forgetting to implement ConstraintValidator interface
- Missing @Target causes compile warnings but not always validation failure
- Empty message() affects error text, not validation logic
@StartsWith that checks if a string starts with a given prefix. Which combination of elements is required to implement this correctly?Solution
Step 1: Define the annotation interface with a prefix parameter
The annotation must declare a methodString prefix()to accept the prefix value.Step 2: Implement the validator class correctly
The validator class must implementConstraintValidator<StartsWith, String>and overrideisValidto check if the string starts with the given prefix.Final Answer:
An annotation interface with a String prefix() method, a validator class implementing ConstraintValidator<StartsWith, String>, and overriding isValid to check the prefix -> Option AQuick Check:
Annotation + validator class + isValid checking prefix = correct [OK]
- Using wrong method names like suffix() for prefix check
- Implementing wrong interfaces or missing annotation interface
- Confusing validate() with isValid() method names
