Bird
Raised Fist0
Spring Bootframework~15 mins

Custom validator annotation in Spring Boot - Deep Dive

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Overview - Custom validator annotation
What is it?
A custom validator annotation in Spring Boot is a way to create your own rules to check if data is valid. Instead of using built-in checks like 'not empty' or 'email format', you write a special annotation and code that decides if the data meets your unique needs. This helps keep your code clean and your validation logic reusable. It works by marking fields or classes with your annotation, and Spring Boot runs your custom checks automatically.
Why it matters
Without custom validator annotations, you would have to write validation code everywhere, mixing it with business logic. This makes code messy and hard to maintain. Custom annotations let you separate validation rules clearly and reuse them across your app. This leads to fewer bugs, easier testing, and better user feedback when data is wrong.
Where it fits
Before learning custom validator annotations, you should know basic Java annotations and Spring Boot's built-in validation with javax.validation (like @NotNull). After mastering custom validators, you can explore advanced validation scenarios, such as cross-field validation or integrating with internationalization for error messages.
Mental Model
Core Idea
A custom validator annotation is a reusable label that tells Spring Boot how to check if data follows your special rules.
Think of it like...
It's like creating a custom stamp that inspectors use to mark if a product passes your unique quality test, instead of just using standard stamps.
┌───────────────────────────────┐
│   Custom Validator Annotation  │
├──────────────┬────────────────┤
│ Annotation   │ @MyCustomCheck │
├──────────────┼────────────────┤
│ Validator    │ MyCustomCheckValidator implements ConstraintValidator │
├──────────────┼────────────────┤
│ Usage        │ @MyCustomCheck on fields or classes │
└──────────────┴────────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding Java Annotations Basics
🤔
Concept: Learn what annotations are and how they add metadata to Java code.
Annotations are special markers you put on classes, methods, or fields to give extra information. For example, @Override tells the compiler you are overriding a method. They don't change code behavior by themselves but can be read by tools or frameworks to do special things.
Result
You can recognize and write simple annotations like @Override or @Deprecated.
Understanding annotations is key because custom validators are built as special annotations that Spring Boot reads to apply validation.
2
FoundationUsing Built-in Validation Annotations
🤔
Concept: See how Spring Boot uses standard annotations like @NotNull to check data.
Spring Boot supports annotations from javax.validation like @NotNull, @Size, and @Email. When you add these to fields, Spring automatically checks input data and reports errors if rules are broken.
Result
You can validate simple rules without writing extra code.
Knowing built-in validation shows the pattern your custom validator will follow and why it's useful to create your own.
3
IntermediateCreating a Custom Annotation Interface
🤔Before reading on: do you think a custom validator annotation needs to be a class or an interface? Commit to your answer.
Concept: Define a new annotation interface with metadata that Spring Boot can recognize.
You create a Java interface annotated with @Constraint and other meta-annotations like @Target and @Retention. This interface defines the annotation name and links to the validator class that will do the checking.
Result
You have a new annotation type, e.g., @MyCustomCheck, ready to use on fields.
Understanding that annotations are interfaces with metadata helps you see how Spring Boot discovers and applies your custom validation.
4
IntermediateImplementing the Validator Class
🤔Before reading on: do you think the validator class should extend a base class or implement an interface? Commit to your answer.
Concept: Write a class that implements ConstraintValidator to hold the validation logic.
The validator class implements ConstraintValidator. It overrides initialize() to get annotation parameters and isValid() to check if the value meets your rules. Return true if valid, false otherwise.
Result
Your validation logic runs automatically when Spring Boot validates data.
Knowing the validator class is where the actual check happens clarifies the separation between annotation metadata and logic.
5
IntermediateApplying Custom Annotation in a Model
🤔
Concept: Use your custom annotation on fields or classes to enforce your rules.
Add @MyCustomCheck above fields in your data model classes. When Spring Boot processes input, it runs your validator and reports errors if validation fails.
Result
Your app rejects invalid data according to your custom rules.
Seeing how the annotation integrates into your model shows the practical use and impact of custom validators.
6
AdvancedCustomizing Error Messages and Parameters
🤔Before reading on: do you think error messages are hardcoded or configurable in custom validators? Commit to your answer.
Concept: Make your annotation accept parameters and support custom error messages.
Add fields like message(), groups(), and payload() to your annotation interface. Use placeholders in messages and pass parameters to the validator. This allows flexible, user-friendly error feedback.
Result
Validation errors show meaningful messages tailored to each case.
Understanding message customization improves user experience and makes your validators more versatile.
7
ExpertCross-Field and Class-Level Validation
🤔Before reading on: do you think custom validators can only check single fields or also multiple fields together? Commit to your answer.
Concept: Create validators that check relationships between multiple fields or entire objects.
Define your annotation to target TYPE (class level) and implement ConstraintValidator. In isValid(), access multiple fields to enforce rules like 'start date before end date'. This requires careful design to avoid side effects.
Result
Your app enforces complex business rules beyond simple field checks.
Knowing how to validate multiple fields together unlocks powerful validation scenarios needed in real apps.
Under the Hood
Spring Boot uses the Bean Validation API (JSR 380) under the hood. When validation runs, it scans the annotated fields and classes, finds the annotations, and looks up the linked ConstraintValidator implementations. It creates instances of these validators, calls initialize() with annotation parameters, then calls isValid() with the actual data. The results determine if validation passes or fails. This happens during data binding or explicit validation calls.
Why designed this way?
This design separates metadata (annotations) from logic (validators), making validation modular and reusable. It leverages Java's annotation processing and reflection to keep code clean and declarative. Alternatives like hardcoded checks would mix validation with business logic, reducing maintainability and reusability.
┌───────────────┐       ┌───────────────────────┐       ┌───────────────┐
│  Data Model   │──────▶│  Custom Annotation     │──────▶│ Validator Class│
│ (with @Anno)  │       │ (interface with meta) │       │ (implements    │
└───────────────┘       └───────────────────────┘       │ ConstraintValidator)│
                                                        └───────────────┘
                                                             │
                                                             ▼
                                                    ┌─────────────────┐
                                                    │ isValid(value)   │
                                                    │ returns true/false│
                                                    └─────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do you think custom validator annotations automatically validate nested objects? Commit to yes or no.
Common Belief:Custom validator annotations automatically validate all nested objects and fields inside the annotated object.
Tap to reveal reality
Reality:Custom validators only validate the specific field or class they are applied to. Nested objects require their own annotations or explicit validation calls.
Why it matters:Assuming automatic nested validation leads to missing errors and unexpected bugs in complex data structures.
Quick: Do you think the validator class can be any class or must implement a specific interface? Commit to your answer.
Common Belief:Any class can be used as a validator as long as it has validation logic inside.
Tap to reveal reality
Reality:Validator classes must implement ConstraintValidator interface for Spring Boot to recognize and run them properly.
Why it matters:Using a class without this interface means your validation logic won't run, causing silent failures.
Quick: Do you think custom validator annotations can change the data they validate? Commit yes or no.
Common Belief:Custom validator annotations can modify the data they validate to fix errors automatically.
Tap to reveal reality
Reality:Validators only check data and return true or false; they do not modify the data itself.
Why it matters:Expecting automatic data correction can lead to overlooked invalid data and security issues.
Quick: Do you think error messages in custom validators are fixed and cannot be customized? Commit your answer.
Common Belief:Error messages in custom validators are hardcoded and cannot be changed without rewriting the validator.
Tap to reveal reality
Reality:Error messages can be customized via annotation parameters and message resource bundles for flexibility and localization.
Why it matters:Believing messages are fixed limits user experience and internationalization possibilities.
Expert Zone
1
Custom validators can be combined with Spring's groups feature to apply different validations in different contexts, which many beginners overlook.
2
The initialize() method in ConstraintValidator is called once per validator instance, so expensive setup should be done there, not in isValid(), to optimize performance.
3
Class-level validators must carefully handle null values and casting to avoid runtime exceptions, a subtlety often missed in production code.
When NOT to use
Avoid custom validator annotations when simple built-in annotations suffice, or when validation logic depends heavily on external services or asynchronous checks. In such cases, consider service-layer validation or reactive validation frameworks instead.
Production Patterns
In real-world apps, custom validators are often used for domain-specific rules like checking unique usernames, validating complex formats, or enforcing business constraints across multiple fields. They are integrated with internationalized messages and combined with Spring's validation groups for flexible validation flows.
Connections
Aspect-Oriented Programming (AOP)
Both use annotations to separate concerns and add behavior declaratively.
Understanding how annotations trigger behavior in AOP helps grasp how custom validator annotations trigger validation logic cleanly.
Design by Contract
Custom validator annotations enforce preconditions on data similar to contracts in software design.
Seeing validation as enforcing contracts clarifies why separating validation logic improves software reliability.
Quality Control in Manufacturing
Custom validator annotations act like quality control checks ensuring products meet standards before use.
Recognizing validation as quality control helps appreciate its role in preventing defects early.
Common Pitfalls
#1Validator class missing ConstraintValidator interface implementation.
Wrong approach:public class MyValidator { public boolean isValid(String value) { return value != null && value.length() > 3; } }
Correct approach:public class MyValidator implements ConstraintValidator { @Override public boolean isValid(String value, ConstraintValidatorContext context) { return value != null && value.length() > 3; } }
Root cause:Not implementing the required interface means Spring Boot cannot recognize or call the validator properly.
#2Applying custom annotation without @Target and @Retention meta-annotations.
Wrong approach:@Constraint(validatedBy = MyValidator.class) public @interface MyAnnotation { String message() default "Invalid"; }
Correct approach:@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = MyValidator.class) public @interface MyAnnotation { String message() default "Invalid"; Class[] groups() default {}; Class[] payload() default {}; }
Root cause:Missing meta-annotations causes the annotation to not be retained at runtime or not applicable to desired elements.
#3Hardcoding error messages inside validator logic instead of annotation.
Wrong approach:public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null) { context.buildConstraintViolationWithTemplate("Value cannot be null").addConstraintViolation(); return false; } return true; }
Correct approach:public boolean isValid(String value, ConstraintValidatorContext context) { return value != null; } // Message defined in annotation or messages.properties file
Root cause:Mixing message text in code reduces flexibility and localization support.
Key Takeaways
Custom validator annotations let you create reusable, declarative rules to check data validity beyond built-in checks.
They separate validation metadata (annotations) from logic (validator classes), keeping code clean and maintainable.
Validators must implement ConstraintValidator interface and be linked properly in the annotation for Spring Boot to run them.
Custom annotations support parameters and customizable error messages for flexible and user-friendly validation.
Advanced validators can check multiple fields or entire objects, enabling complex business rule enforcement.

Practice

(1/5)
1. What is the main purpose of creating a custom validator annotation in Spring Boot?
easy
A. To define your own validation rules reusable across your application
B. To replace built-in annotations like @NotNull completely
C. To automatically generate database tables
D. To handle HTTP requests in controllers

Solution

  1. Step 1: Understand the role of custom validator annotations

    They allow you to create your own rules for validating data beyond built-in checks.
  2. Step 2: Identify the main benefit

    These annotations keep validation logic clean and reusable across different parts of your app.
  3. Final Answer:

    To define your own validation rules reusable across your application -> Option A
  4. Quick Check:

    Custom validator = reusable validation rules [OK]
Hint: Custom validators create reusable rules, not replace built-ins [OK]
Common Mistakes:
  • Thinking custom validators replace all built-in annotations
  • Confusing validation with database or HTTP handling
  • Assuming custom validators auto-generate code
2. Which of the following is the correct way to declare a custom validator annotation interface in Spring Boot?
easy
A. @Validator class MyValidator { String message() default "Invalid"; }
B. class MyValidator { String message() default "Invalid"; }
C. interface MyValidator { void validate(); }
D. @interface MyValidator { String message() default "Invalid"; Class[] groups() default {}; Class[] payload() default {}; }

Solution

  1. Step 1: Recall the syntax for custom annotation interfaces

    They use @interface keyword and define methods like message(), groups(), and payload().
  2. Step 2: Check each option

    @interface MyValidator { String message() default "Invalid"; Class[] groups() default {}; Class[] payload() default {}; } correctly uses @interface and includes required methods. Others either use wrong keywords or miss required parts.
  3. Final Answer:

    @interface MyValidator { String message() default "Invalid"; Class[] groups() default {}; Class[] payload() default {}; } -> Option D
  4. Quick Check:

    Custom annotation = @interface + standard methods [OK]
Hint: Custom annotations use @interface with message, groups, payload [OK]
Common Mistakes:
  • Using class or interface instead of @interface
  • Omitting groups() or payload() methods
  • Adding methods unrelated to validation
3. Given this validator class snippet, what will happen when validating a string with value "abc123"?
public class AlphaValidator implements ConstraintValidator<Alpha, String> {
  public boolean isValid(String value, ConstraintValidatorContext context) {
    return value != null && value.matches("^[a-zA-Z]+$");
  }
}
medium
A. Validation throws a NullPointerException
B. Validation passes because the string contains letters
C. Validation fails because the string contains digits
D. Validation always returns true regardless of input

Solution

  1. 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.
  2. Step 2: Test the input "abc123" against the regex

    Since "abc123" contains digits, it does not match the regex, so the method returns false.
  3. Final Answer:

    Validation fails because the string contains digits -> Option C
  4. Quick Check:

    Regex allows only letters, digits cause failure [OK]
Hint: Check regex carefully; digits break letter-only pattern [OK]
Common Mistakes:
  • Assuming partial match passes validation
  • Ignoring null check in code
  • Thinking digits are allowed by regex
4. You wrote a custom validator but it always passes validation even for invalid data. Which of these is the most likely cause?
medium
A. The annotation interface is missing the @Target annotation
B. The isValid method always returns true regardless of input
C. The validator class does not implement ConstraintValidator
D. The message() method in the annotation returns an empty string

Solution

  1. Step 1: Understand the role of isValid method

    This method contains the validation logic and must return true only for valid inputs.
  2. Step 2: Identify why validation always passes

    If isValid always returns true, invalid data will pass unchecked.
  3. Final Answer:

    The isValid method always returns true regardless of input -> Option B
  4. Quick Check:

    isValid controls validation result; always true means always pass [OK]
Hint: Check isValid method return values first when validation fails [OK]
Common Mistakes:
  • Forgetting to implement ConstraintValidator interface
  • Missing @Target causes compile warnings but not always validation failure
  • Empty message() affects error text, not validation logic
5. You want to create a custom validator annotation @StartsWith that checks if a string starts with a given prefix. Which combination of elements is required to implement this correctly?
hard
A. An annotation interface with a String prefix() method, a validator class implementing ConstraintValidator<StartsWith, String>, and overriding isValid to check the prefix
B. An annotation interface with int length(), a validator class implementing Validator, and overriding validate to check length
C. A class annotated with @Component that implements ConstraintValidator without an annotation interface
D. An annotation interface with String suffix(), a validator class implementing ConstraintValidator<EndsWith, String>, and overriding isValid to check suffix

Solution

  1. Step 1: Define the annotation interface with a prefix parameter

    The annotation must declare a method String prefix() to accept the prefix value.
  2. Step 2: Implement the validator class correctly

    The validator class must implement ConstraintValidator<StartsWith, String> and override isValid to check if the string starts with the given prefix.
  3. 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 A
  4. Quick Check:

    Annotation + validator class + isValid checking prefix = correct [OK]
Hint: Match annotation method and validator generic types carefully [OK]
Common Mistakes:
  • Using wrong method names like suffix() for prefix check
  • Implementing wrong interfaces or missing annotation interface
  • Confusing validate() with isValid() method names