Implementing Method-Level Parameter Validation with Custom Annotations in SpringBoot

This article demonstrates how to create a reusable, method‑level parameter validation mechanism in SpringBoot using custom @Check annotations, covering the motivation, usage examples, Maven dependencies, annotation definition, core AOP logic, validation algorithm, and future enhancements.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Implementing Method-Level Parameter Validation with Custom Annotations in SpringBoot

This article introduces a generic interface parameter validation solution built on SpringBoot and JDK8, leveraging custom annotations to perform method‑level checks.

Motivation

Typical validation adds annotations to entity classes, which works only when the same rules apply to all methods. For different business scenarios—e.g., a user registration requiring both name and age, while login only needs name—class‑level validation is unsuitable. Therefore a method‑level validation approach is needed.

Usage Example

Consider the following AccountVO entity:

public class AccountVO {
    private String name; // 姓名
    private Integer age; // 年龄
}

In a service implementation, the @Check annotation specifies which fields must be validated for a particular method:

@Service
public class TestImpl implements ITestService {

    @Override
    @Check({"name", "age"})
    public void testValid(AccountVO vo) {
        // ...
    }
}

The annotation can also express more complex rules such as non‑null, size limits, or equality checks: @Check({"id>=8", "name!=aaa", "title<10"}) When validation fails, a default error message is generated, e.g.:

updateUserId must not null while calling testValid
id must >=8 while calling testValid
name must != aaa while calling testValid

Custom error messages can be provided by appending a colon and the message:

@Check({"title<=8:标题字数不超过8个字,含标点符号"})

Maven Dependencies

Besides the SpringBoot starter, the following third‑party libraries are required:

<!-- 用于字符串校验 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.3.2</version>
</dependency>

<!-- 用于日志打印 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

Custom Annotation Definition

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Parameter validation annotation
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RUNTIME)
public @interface Check {
    /**
     * Validation rules: fieldName+operator+value[:customMessage]
     */
    String[] value();
}

Core AOP Logic

The aspect intercepts methods annotated with @Check and performs validation before method execution. If any rule fails, an IllegalArgumentException is thrown.

@Around(value = "@com.cipher.checker.Check")
public Object check(ProceedingJoinPoint point) throws Throwable {
    String msg = doCheck(point);
    if (!StringUtils.isEmpty(msg)) {
        throw new IllegalArgumentException(msg);
    }
    Object obj = point.proceed();
    return obj;
}

Validation Method (doCheck)

The doCheck method extracts the method, its @Check annotation, the target object, and iterates over each field rule to evaluate it via reflection.

private String doCheck(ProceedingJoinPoint point) {
    Object[] arguments = point.getArgs();
    Method method = getMethod(point);
    String methodInfo = StringUtils.isEmpty(method.getName()) ? "" : " while calling " + method.getName();
    String msg = "";
    if (isCheck(method, arguments)) {
        Check annotation = method.getAnnotation(Check.class);
        String[] fields = annotation.value();
        Object vo = arguments[0];
        if (vo == null) {
            msg = "param can not be null";
        } else {
            for (String field : fields) {
                FieldInfo info = resolveField(field, methodInfo);
                Object value = ReflectionUtil.invokeGetter(vo, info.field);
                Boolean isValid = info.optEnum.fun.apply(value, info.operatorNum);
                msg = isValid ? msg : info.innerMsg;
            }
        }
    }
    return msg;
}

Operator Enum

The enum defines supported operators (>, >=, <, <=, !=, not null) and associates each with a validation function.

enum Operator {
    GREATER_THAN(">", CheckParamAspect::isGreaterThan),
    GREATER_THAN_EQUAL(">=", CheckParamAspect::isGreaterThanEqual),
    LESS_THAN("<", CheckParamAspect::isLessThan),
    LESS_THAN_EQUAL("<=", CheckParamAspect::isLessThanEqual),
    NOT_EQUAL("!=", CheckParamAspect::isNotEqual),
    NOT_NULL("not null", CheckParamAspect::isNotNull);
    // constructor and fields omitted for brevity
}

TODO

Package the solution as a Spring Boot Starter component.

Add support for regular‑expression based validation.

Author: cipher – Original article

Source code: GitHub repository

Recent Technical Articles

SpringBoot integration with Activiti workflow (with source)

Deep dive into SpringBoot + Spring Kafka integration

Alibaba's 29 impressive open‑source projects

Animated demo of JDK8 data structures

Gracefully stop a SpringBoot service without kill -9

Lightweight message queue with RedisTemplate

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.

aopCustom AnnotationSpringBootParameter Validation
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

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.