Backend Development 9 min read

How to Create and Use Custom Annotations for Field, Method, and Class Validation in Java

This article explains how to define custom Java annotations for field, method, and class validation, demonstrates the required @Target and @Retention settings, shows how to implement a ConstraintValidator, and provides practical Spring examples for permission checks and caching using interceptors and aspects.

Top Architect
Top Architect
Top Architect
How to Create and Use Custom Annotations for Field, Method, and Class Validation in Java

During business development we often encounter various annotations, but the built‑in ones may not satisfy complex requirements, so we can define custom annotations to meet our needs. The article is divided into field annotations, method annotations, and class annotations.

Field Annotation

Field annotations are typically used for validation, e.g., the hibernate‑validate dependency provides annotations like @NotNull and @Range , but they cannot cover all scenarios such as restricting a String field to a specific set of values.

Custom Annotation

Define an annotation @Check using @interface :

@Target({ ElementType.FIELD }) // only on fields
@Retention(RetentionPolicy.RUNTIME) // retained at runtime for reflection
@Constraint(validatedBy = ParamConstraintValidated.class)
public @interface Check {
    /** valid parameter values */
    String[] paramValues();

    /** error message */
    String message() default "参数不为指定值";

    Class
[] groups() default {};
    Class
[] payload() default {};
}

@Target specifies where the annotation can be applied (e.g., ElementType.FIELD for fields). @Retention defines the lifecycle, with RetentionPolicy.RUNTIME allowing reflection access.

Validator Class

The validator must implement ConstraintValidator :

public class ParamConstraintValidated implements ConstraintValidator
{
    private List
paramValues;

    @Override
    public void initialize(Check constraintAnnotation) {
        paramValues = Arrays.asList(constraintAnnotation.paramValues());
    }

    @Override
    public boolean isValid(Object o, ConstraintValidatorContext ctx) {
        if (paramValues.contains(o)) {
            return true;
        }
        return false;
    }
}

Use the annotation on an entity field and add @Validated (or @Valid ) on the controller method to trigger validation.

Method and Class Annotations

Custom annotations can also control method or class behavior, such as permission checks or multi‑level caching. Example scenarios include restricting access to certain users or looking up data first in Guava cache, then Redis, and finally MySQL.

Permission Annotation

Custom Annotation

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionCheck {
    /** resource key */
    String resourceKey();
}

The annotation can be placed on classes or methods.

Interceptor

public class PermissionCheckInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        PermissionCheck permission = findPermissionCheck(handlerMethod);
        if (permission == null) return true;
        String resourceKey = permission.resourceKey();
        // simple demo: allow only "testKey"
        if ("testKey".equals(resourceKey)) return true;
        return false;
    }

    private PermissionCheck findPermissionCheck(HandlerMethod handlerMethod) {
        PermissionCheck permission = handlerMethod.getMethodAnnotation(PermissionCheck.class);
        if (permission == null) {
            permission = handlerMethod.getBeanType().getAnnotation(PermissionCheck.class);
        }
        return permission;
    }
}

When the method is annotated with @PermissionCheck(resourceKey="test") , the interceptor checks the key and permits or denies access.

Cache Annotation

Custom Annotation

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomCache {
    /** cache key */
    String key();
}

Aspect

@Aspect
@Component
public class CustomCacheAspect {
    @Around("@annotation(com.cqupt.annotation.CustomCache) && @annotation(customCache)")
    public Object dealProcess(ProceedingJoinPoint pjd, CustomCache customCache) {
        if (customCache.key() == null) {
            // TODO: throw error
        }
        // simple demo: if key is "testKey" return a fixed value
        if ("testKey".equals(customCache.key())) {
            return "hello word";
        }
        Object result = null;
        try {
            result = pjd.proceed();
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return result;
    }
}

The aspect processes the annotation before method execution; if the key matches a predefined value, it can return a cached response directly.

Both permission and cache examples include simple test controller methods demonstrating how to apply the custom annotations.

backendJavaSpringvalidationCustom AnnotationAspect-Oriented Programming
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

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