When making apps, sometimes we need to stop kids under 18 from signing up. Instead of writing the same rule everywhere, we can make a special tag (annotation) to check age easily. Let's learn how to do it step by step!
Why Use Custom Annotations?
Spring Boot has built-in checks like @NotNull
(not empty) and @Size
(length), but not for age. Instead of writing the same age-checking code again and again, we create a custom annotation that we can use anywhere in the app.
Steps to Create an Age Validator
Step 1: Create the Annotation
This is like making a new sticker that says "Check Age" which we can put on our data fields.
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = AgeValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MinAge {
int value() default 18;
String message() default "You must be at least {value} years old";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Understanding the Annotations Used
@Constraint(validatedBy = AgeValidator.class)
: Links the annotation to theAgeValidator
class, which contains the logic to validate the age.@Target({ElementType.FIELD, ElementType.PARAMETER})
: Specifies where we can use this annotation. Options include:ElementType.FIELD
: Can be applied to fields in a class.ElementType.PARAMETER
: Can be used on method parameters.- Other options:
METHOD
,TYPE
,ANNOTATION_TYPE
, etc.
@Retention(RetentionPolicy.RUNTIME)
: Defines when the annotation is available. Options include:RetentionPolicy.RUNTIME
: The annotation is accessible during runtime (needed for validation).RetentionPolicy.CLASS
: Available in the class file but not at runtime.RetentionPolicy.SOURCE
: Only used in source code and discarded by the compiler.
Step 2: Write the Age Checking Logic
This part calculates the age and tells if it's 18 or more.
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.time.LocalDate;
import java.time.Period;
public class AgeValidator implements ConstraintValidator<MinAge, LocalDate> {
private int minAge;
@Override
public void initialize(MinAge constraintAnnotation) {
this.minAge = constraintAnnotation.value();
}
@Override
public boolean isValid(LocalDate dob, ConstraintValidatorContext context) {
if (dob == null) {
return false; // No date means invalid
}
return Period.between(dob, LocalDate.now()).getYears() >= minAge;
}
}
Step 3: Use the Annotation in a User Data Class
Now, we use @MinAge
to check age whenever someone signs up.
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
public class UserDTO {
@NotNull(message = "Please enter your birthdate")
@MinAge(18)
private LocalDate dateOfBirth;
// Getters and Setters
public LocalDate getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(LocalDate dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
}
Step 4: Apply Validation in a Controller
When a new user signs up, we check their age automatically.
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping("/register")
public ResponseEntity<String> registerUser(@RequestBody @Valid UserDTO userDTO) {
return ResponseEntity.ok("User registered successfully");
}
}
Step 5: Test the Validation
If someone younger than 18 tries to sign up, they will see this message:
{
"dateOfBirth": "You must be at least 18 years old"
}
Making Sure Name is Lowercase
Sometimes, we want names to be stored in lowercase automatically. There are two ways to do this:
Option 1: Use @ColumnTransformer
(Hibernate)
If using Hibernate, we can transform the value before saving.
import org.hibernate.annotations.ColumnTransformer;
@Entity
public class User {
@ColumnTransformer(write = "lower(?)")
private String name;
}
Option 2: Custom Annotation for Lowercase
If we want to ensure lowercase format, we can create a custom annotation.
Step 1: Create the Annotation
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = LowercaseValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lowercase {
String message() default "Must be lowercase";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Step 2: Create the Validator
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class LowercaseValidator implements ConstraintValidator<Lowercase, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && value.equals(value.toLowerCase());
}
}
Step 3: Use the Annotation
public class UserDTO {
@Lowercase
private String name;
}
Recommended Approach
- If using Hibernate,
@ColumnTransformer(write = "lower(?)")
is simple and works well. - If working with validations, a custom
@Lowercase
annotation ensures that input is already correct. - A hybrid approach: Apply both for best consistency.
Conclusion
By making a custom @MinAge
annotation, we ensure kids under 18 cannot register. Similarly, using a @Lowercase
annotation or Hibernate’s built-in transformation helps maintain data consistency. These techniques keep our code clean, reusable, and easy to maintain.
Hope this helps! Happy coding! 🚀