Backend Development 9 min read

Creating and Using Custom Annotations in Spring for Validation, Permission, and Caching

This article explains how to create and use custom Java annotations in Spring for field validation, permission checks, and caching, including the definition of annotation types, associated validator and interceptor classes, and practical usage examples with code snippets.

IT Xianyu
IT Xianyu
IT Xianyu
Creating and Using Custom Annotations in Spring for Validation, Permission, and Caching

Custom Annotations Overview

During backend development we often encounter built‑in annotations that cannot satisfy complex business requirements, so we can define our own annotations to fill the gap. The article is divided into sections for field, method, and class annotations.

Field Annotations

Field annotations are typically used for validation. The hibernate‑validator library provides many such annotations (e.g., @NotNull , @Range ), but custom validation may be needed when a value must belong to a specific String collection.

Define a custom annotation @Check :

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ParamConstraintValidated.class)
public @interface Check {
    String[] paramValues();
    String message() default "参数不为指定值";
    Class
[] groups() default {};
    Class
[] payload() default {};
}

The @Target meta‑annotation specifies where the annotation can be placed (FIELD, METHOD, TYPE, etc.). @Retention(RetentionPolicy.RUNTIME) keeps the annotation available at runtime for reflection.

Validator class 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) {
        return paramValues.contains(o);
    }
}

Usage example with an entity:

@Data
public class User {
    private String name;
    @Check(paramValues = {"man", "woman"})
    private String sex;
}

When the User object is validated (e.g., in a Spring MVC controller), the sex field must be either "man" or "woman".

Method and Class Annotations

Custom annotations can also control access or caching. Two typical scenarios are permission checks and cache handling.

Permission Annotation

Define the annotation:

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

Implement an interceptor that reads the annotation and decides whether to allow the request:

public class PermissionCheckInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod hm = (HandlerMethod) handler;
        PermissionCheck pc = findPermissionCheck(hm);
        if (pc == null) return true;
        String key = pc.resourceKey();
        // Simple demo: only "testKey" passes
        return "testKey".equals(key);
    }
    private PermissionCheck findPermissionCheck(HandlerMethod hm) {
        PermissionCheck pc = hm.getMethodAnnotation(PermissionCheck.class);
        if (pc == null) pc = hm.getBeanType().getAnnotation(PermissionCheck.class);
        return pc;
    }
}

Apply it to a controller method:

@GetMapping("/api/test")
@PermissionCheck(resourceKey = "test")
public Object testPermissionCheck() {
    return "hello world";
}

Cache Annotation

Define a custom cache annotation:

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

Use an Aspect to handle the annotation before method execution:

@Aspect
@Component
public class CustomCacheAspect {
    @Around("@annotation(com.cqupt.annotation.CustomCache) && @annotation(customCache)")
    public Object dealProcess(ProceedingJoinPoint pjd, CustomCache customCache) throws Throwable {
        if (customCache.key() == null) {
            // TODO: throw error
        }
        if ("testKey".equals(customCache.key())) {
            return "hello word";
        }
        Object result = pjd.proceed();
        return result;
    }
}

Example usage:

@GetMapping("/api/cache")
@CustomCache(key = "test")
public Object testCustomCache() {
    return "don't hit cache";
}

Testing Controllers

A simple test controller demonstrates validation:

@RestController("/api/test")
public class TestController {
    @PostMapping
    public Object test(@Validated @RequestBody User user) {
        return "hello world";
    }
}

Note that the User class must be annotated with @Validated (or @Valid ) for the custom field annotation to take effect.

Conclusion

By defining custom annotations, their target locations, retention policies, and associated processing components (validators, interceptors, aspects), developers can extend Spring’s capabilities to meet sophisticated business logic such as parameter validation, permission enforcement, and multi‑level caching.

JavaSpringcachingpermissionvalidationannotations
IT Xianyu
Written by

IT Xianyu

We share common IT technologies (Java, Web, SQL, etc.) and practical applications of emerging software development techniques. New articles are posted daily. Follow IT Xianyu to stay ahead in tech. The IT Xianyu series is being regularly updated.

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.