Master Parameter & Business Rule Validation in SpringBoot Using Custom Bean Validation
Learn how to implement both simple parameter checks and complex business rule validations in SpringBoot by leveraging Bean Validation, creating custom annotations like @UniqueUser and @NotConflictUser, and integrating them seamlessly into controllers to ensure data integrity and reduce boilerplate logic.
Hello, I am Su San.
In everyday API development we need to handle two kinds of validation to ensure stability and security: parameter validation and business rule validation.
Parameter Validation
Parameter validation is straightforward, e.g., checking that username and password are not empty during login or that email and phone formats are correct when creating a user.
Implementation is simple with the Bean Validation framework, which provides a rich set of constraint annotations.
Common validation annotations include:
@Null, @NotNull, @AssertTrue, @AssertFalse, @Min, @Max, @DecimalMin, @DecimalMax, @Negative, @NegativeOrZero, @Positive, @PositiveOrZero, @Size, @Digits, @Past, @PastOrPresent, @Future, @FutureOrPresent, @Pattern, @NotEmpty, @NotBlank, @EmailBusiness Rule Validation
Business rule validation ensures that an interface satisfies specific domain constraints, such as guaranteeing the uniqueness of a user's name, phone number, or email across the system.
Typical service‑level code checks for existing records and throws an exception when a conflict is found:
public void create(User user) {
Account account = accountDao.queryByUserNameOrPhoneOrEmail(user.getName(), user.getPhone(), user.getEmail());
if (account != null) {
throw new IllegalArgumentException("User already exists, please re‑enter");
}
}A more elegant approach is to use Bean Validation with custom annotations to move these checks out of service logic.
Custom Annotations
UniqueUser: ensures a user’s username, phone number, and email are unique.
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.UniqueUserValidator.class)
public @interface UniqueUser {
String message() default "用户名、手机号码、邮箱不允许与现存用户重复";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
} NotConflictUser: ensures a user’s information does not conflict with other existing users.
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.NotConflictUserValidator.class)
public @interface NotConflictUser {
String message() default "用户名称、邮箱、手机号码与现存用户产生重复";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}Implementing Validation Rules
To make custom annotations work, implement the ConstraintValidator interface. The first generic type is the annotation, the second is the type to validate (the User object).
public class UserValidation<T extends Annotation> implements ConstraintValidator<T, User> {
protected Predicate<User> predicate = c -> true;
@Resource
protected UserRepository userRepository;
@Override
public boolean isValid(User user, ConstraintValidatorContext ctx) {
return userRepository == null || predicate.test(user);
}
public static class UniqueUserValidator extends UserValidation<UniqueUser> {
@Override
public void initialize(UniqueUser annotation) {
predicate = c -> !userRepository.existsByUserNameOrEmailOrTelphone(c.getUserName(), c.getEmail(), c.getTelphone());
}
}
public static class NotConflictUserValidator extends UserValidation<NotConflictUser> {
@Override
public void initialize(NotConflictUser annotation) {
predicate = c -> {
Collection<User> collection = userRepository.findByUserNameOrEmailOrTelphone(c.getUserName(), c.getEmail(), c.getTelphone());
return collection.isEmpty() || (collection.size() == 1 && collection.iterator().next().getId().equals(c.getId()));
};
}
}
}Usage
@RestController
@RequestMapping("/senior/user")
@Slf4j
@Validated
public class UserController {
@Autowired
private UserRepository userRepository;
@PostMapping
public User createUser(@UniqueUser @Valid User user) {
User saved = userRepository.save(user);
log.info("save user id is {}", saved.getId());
return saved;
}
@SneakyThrows
@PutMapping
public User updateUser(@NotConflictUser @Valid @RequestBody User user) {
User edited = userRepository.save(user);
log.info("update user is {}", edited);
return edited;
}
}Simply add the custom annotation to a method parameter; no additional business‑logic code is required.
Test Result
{
"status": 400,
"message": "用户名、手机号码、邮箱不允许与现存用户重复",
"data": null,
"timestamp": 1644309081037
}Conclusion
By separating validation from business logic with @Validated and custom Bean Validation annotations, you achieve elegant, reusable checks that can be applied at any layer—controller, service, or repository—without cluttering core code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
