Master Java Annotations: From Basics to Custom Usage
This article provides a comprehensive guide to Java annotations, covering built‑in annotations, meta‑annotations, retention policies, repeatable annotations, and how to create and apply custom annotations with reflection and AOP for practical use cases.
1 Java Annotation Basics
Annotations were introduced in JDK 1.5 to add metadata to packages, classes, interfaces, fields, method parameters, and local variables. Their main purposes are generating Javadoc, compile‑time checking, compile‑time dynamic processing, and runtime dynamic processing.
Generate Javadoc documentation by marking metadata.
Compile‑time checks using metadata for validation.
Compile‑time dynamic handling such as code generation.
Runtime dynamic handling via reflection to inject instances.
Annotations are classified into three categories:
Standard Java annotations – e.g., @Override, @Deprecated, @SuppressWarnings.
Meta‑annotations – annotations that annotate other annotations, such as @Retention, @Target, @Documented, @Inherited, @Repeatable, @Native.
Custom annotations – user‑defined annotations that can also be meta‑annotated.
1.1 Built‑in Annotations
<code>class Parent {
public void rewriteMethod() {}
}
class Child extends Parent {
/**
* Override parent method
*/
@Override
public void rewriteMethod() {}
/**
* Deprecated method
*/
@Deprecated
public void oldMethod() {}
/**
* Suppress warnings
*/
@SuppressWarnings("keep run")
public List infoList() {
List list = new ArrayList();
return list;
}
}</code>Standard annotations include:
@Override : indicates the method overrides a superclass method.
@Deprecated : marks the element as deprecated, causing a compiler warning.
@SuppressWarnings : disables specified compiler warnings.
1.2 Meta‑annotations
Four standard meta‑annotations were added in JDK 1.5: @Target, @Retention, @Documented, @Inherited. JDK 8 added @Repeatable and @Native.
@Target
Describes where an annotation can be applied (packages, types, class members, method parameters, local variables).
<code>public enum ElementType {
TYPE, // class, interface, enum
FIELD, // member variable
METHOD, // member method
PARAMETER, // method parameter
CONSTRUCTOR, // constructor
LOCAL_VARIABLE, // local variable
ANNOTATION_TYPE, // annotation type
PACKAGE, // package
TYPE_PARAMETER, // type parameter (Java 8)
TYPE_USE // any use of a type (Java 8)
}</code>@Retention
Specifies how long annotations are retained: SOURCE, CLASS, or RUNTIME.
<code>public enum RetentionPolicy {
SOURCE, // discarded by compiler
CLASS, // stored in .class file (default)
RUNTIME // retained at runtime, accessible via reflection
}</code>Example of three retention policies:
<code>@Retention(RetentionPolicy.SOURCE)
public @interface SourcePolicy {}
@Retention(RetentionPolicy.CLASS)
public @interface ClassPolicy {}
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimePolicy {}
</code>When applied to methods, the compiler records annotations differently:
SourcePolicy annotations are not recorded in bytecode.
ClassPolicy annotations appear in RuntimeInvisibleAnnotations .
RuntimePolicy annotations appear in RuntimeVisibleAnnotations .
@Documented
Ensures the annotation is included in Javadoc.
<code>@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DocAnnotation {
String value() default "default";
}
</code>@Inherited
If a superclass is annotated with an @Inherited annotation, subclasses inherit it.
<code>@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface InheritedAnnotation {
String[] values();
int number();
}
@InheritedAnnotation(values={"brand"}, number=100)
public class UserInfo {}
public class Customer extends UserInfo {
@Test
public void testMethod() {
Class clazz = Student.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
}
</code>Running the above prints the inherited annotation on Customer .
@Repeatable (Java 8)
Allows multiple instances of the same annotation on a single element.
<code>@Repeatable(Pets.class)
public @interface Pet {
String myPet();
}
public @interface Pets {
Pet[] value();
}
@Pets({@Pet(myPet="dog"), @Pet(myPet="cat")})
public class RepeatAnnotationOV {
public void workMethod() {}
}
@Pet(myPet="dog")
@Pet(myPet="cat")
public class RepeatAnnotationNV {
public void workMethod() {}
}
</code>@Native (Java 8)
Marks a field as usable by native code; rarely used.
1.3 Accessing Annotations via Reflection
The java.lang.reflect.AnnotatedElement interface provides methods to query annotations at runtime, but only if the annotation’s retention is RUNTIME.
isAnnotationPresent(Class<? extends Annotation>)
getAnnotation(Class<? extends Annotation>)
getAnnotations()
getAnnotationsByType(Class<? extends Annotation>)
getDeclaredAnnotation(Class<? extends Annotation>)
getDeclaredAnnotationsByType(Class<? extends Annotation>)
getDeclaredAnnotations()
1.4 Custom Annotations
After understanding built‑in and meta‑annotations, you can define your own.
<code>package com.helenlyn.common.annotation;
import java.lang.annotation.*;
/**
* Fruit provider annotation
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
int id() default -1;
String name() default "";
String address() default "";
}
</code> <code>package com.helenlyn.common.dto;
import com.helenlyn.common.annotation.*;
public class AppleDto {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor=FruitColor.Color.RED)
private String appleColor;
@FruitProvider(id=1, name="helenlyn 贸易公司", address="福州xx路xxx大楼")
private String appleProvider;
}
</code>Utility class using reflection to read these annotations:
<code>public class FruitInfoUtil {
public static String getFruitInfo(Class<?> clazz) {
String name = "水果名称:";
String color = "水果颜色:";
String provider = "供应商信息:";
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(FruitName.class)) {
FruitName fn = field.getAnnotation(FruitName.class);
name += fn.value();
} else if (field.isAnnotationPresent(FruitColor.class)) {
FruitColor fc = field.getAnnotation(FruitColor.class);
color += fc.fruitColor().toString();
} else if (field.isAnnotationPresent(FruitProvider.class)) {
FruitProvider fp = field.getAnnotation(FruitProvider.class);
provider = "供应商编号:" + fp.id() + " 供应商名称:" + fp.name() + " 供应商地址:" + fp.address();
}
}
return String.format("%s;%s;%s;", name, color, provider);
}
}
</code>Running the utility prints the fruit name, color, and provider details.
2 Understanding Annotation Principles
2.1 New Annotations in Java 8
@Repeatable
ElementType.TYPE_USE
ElementType.TYPE_PARAMETER
<code>// Example of TYPE_USE annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface MyNotNull {}
public class Example<T> {
public @MyNotNull T test(@MyNotNull T a) {
new ArrayList<@MyNotNull String>();
return a;
}
}
</code>2.2 Do Annotations Support Inheritance?
Annotations themselves cannot extend another annotation, but a class can inherit annotations from its superclass if the annotation is marked with @Inherited.
3 Annotation Use Cases
Custom Annotations with AOP
Using a custom @DataSource annotation together with Spring AOP to switch data sources dynamically.
<code>@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String name() default "";
}
</code> <code>@Aspect
@Component
public class DataSourceAspect implements Ordered {
@Pointcut("@annotation(com.helenlyn.dataassist.annotation.DataSource)")
public void dataSourcePointCut() {}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource ds = method.getAnnotation(DataSource.class);
String routeKey = ds.name();
String current = DynamicDataSourceRouteHolder.getDataSourceRouteKey();
if (StringUtils.isNotEmpty(current)) {
routeKey = ds.name();
}
DynamicDataSourceRouteHolder.setDataSourceRouteKey(routeKey);
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
DynamicDataSourceRouteHolder.clearDataSourceRouteKey();
}
}
@Override
public int getOrder() { return 1; }
}
</code>Controller methods can now be annotated with @DataSource to route to different databases.
Architecture & Thinking
🍭 Frontline tech director and chief architect at top-tier companies 🥝 Years of deep experience in internet, e‑commerce, social, and finance sectors 🌾 Committed to publishing high‑quality articles covering core technologies of leading internet firms, application architecture, and AI breakthroughs.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.