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.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Master Parameter & Business Rule Validation in SpringBoot Using Custom Bean Validation

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, @Email

Business 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentBean ValidationSpringBootParameter ValidationCustom AnnotationsBusiness Rule Validation
Su San Talks Tech
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.