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
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
Ready to prove it?
Pass the test and this pattern moves unknown
