Generally in Java, an annotation is termed as meta-annotation if it is used on another annotation. You have probably seen such annotations many times. For example: @Document, @Inherited etc. There's no specific declaration needed for an annotation to become a meta-annotation, i.e. any annotation which has declared its @Target with ElementType.TYPE can be meta-annotated on other annotation definitions.
Spring provides many such annotations, for example @RequestMapping variants. The main purpose of using such meta-annotation in Spring is to group/compose multiple annotations together to ease the configuration meta-data on the developer side.
Creating a custom annotation meta-annotated with other annotations
We can create our own annotations which can be annotated with Spring meta-annotations, without providing any custom annotation-processor for that. Spring implicitly recognizes meta-annotations and delegates the processing to the existing related processors.
In this simple example we are going to create annotation 'ResourceGone' which will be meta-annotated with @RequestMapping, @ResponseStatus(HttpStatus.GONE) and @ResponseBody. The purpose of this annotation would be to avoid repeating same set of annotations on multiple controller methods whenever a resource does not exist anymore.
Creating the annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
@ResponseStatus(HttpStatus.GONE)
@ResponseBody
public @interface ResourceGone {
@AliasFor(annotation = RequestMapping.class, attribute = "path")
String[] value () default {};
}
The controller
package com.logicbig.example;
import org.springframework.stereotype.Controller;
@Controller
public class TheController {
@ResourceGone(value = "/link1")
public String handle1 () {
return "The resource 'link1' doesn't exist anymore";
}
@ResourceGone(value = "/link2")
public String handle2 () {
return "The resource 'link2' doesn't exist anymore";
}
}
Running application
To try examples, run embedded Jetty (configured in pom.xml of example project below):
mvn jetty:run
$ curl -s -i http://localhost:8080/link1 HTTP/1.1 410 Gone Server: Jetty(12.1.6) Date: Sat, 21 Mar 2026 12:40:32 GMT Content-Type: text/plain;charset=iso-8859-1 Content-Length: 42
The resource 'link1' doesn't exist anymore
$ curl -s -i http://localhost:8080/link2 HTTP/1.1 410 Gone Server: Jetty(12.1.6) Date: Sat, 21 Mar 2026 12:40:37 GMT Content-Type: text/plain;charset=iso-8859-1 Content-Length: 42
The resource 'link2' doesn't exist anymore
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 4.2.0.RELEASE - 7.0.6 Version compatibilities of spring-webmvc with this example: Versions in green have been tested.
- jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
- JDK 25
- Maven 3.9.11
|