On this page
Annotations
Annotations provide metadata about program elements — classes, methods, fields, and parameters — without directly affecting program semantics. They are widely used by frameworks like Spring, JUnit, and JPA.
Built-in Annotations
| Annotation | Purpose |
|---|---|
@Override |
Marks a method as overriding a superclass method |
@Deprecated |
Marks an element as obsolete |
@SuppressWarnings |
Suppresses compiler warnings |
@FunctionalInterface |
Marks an interface as having exactly one abstract method |
@SafeVarargs |
Asserts that a varargs method does not perform unsafe operations |
@Override
public String toString() {
return "MyClass";
}
@Deprecated(since = "17", forRemoval = true)
public void oldMethod() { }
Defining Custom Annotations
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Author {
String name();
String date() default "unknown";
}
Usage:
@Author(name = "Alice", date = "2024-01-15")
public class Article {
@Author(name = "Bob")
public void publish() { }
}
Meta-Annotations
Annotations that apply to other annotations:
| Meta-annotation | Purpose |
|---|---|
@Retention |
How long the annotation is kept (SOURCE, CLASS, RUNTIME) |
@Target |
Where the annotation can be applied |
@Documented |
Include in generated Javadoc |
@Inherited |
Subclasses inherit the annotation |
@Repeatable |
Allows multiple instances of the same annotation |
Retention Policies
- SOURCE — Discarded by the compiler (e.g.,
@Override) - CLASS — Stored in
.classfiles but not available at runtime (default) - RUNTIME — Available at runtime via reflection (used by frameworks)
Reading Annotations at Runtime
public class AnnotationReader {
public static void main(String[] args) throws Exception {
Class<?> clazz = Article.class;
if (clazz.isAnnotationPresent(Author.class)) {
Author author = clazz.getAnnotation(Author.class);
System.out.println("Author: " + author.name());
}
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Author.class)) {
Author a = method.getAnnotation(Author.class);
System.out.println(method.getName() + " by " + a.name());
}
}
}
}
Marker, Single-Value, and Full Annotations
// Marker — no elements
public @interface Test { }
// Single-value — shorthand when only one element named "value"
public @interface Version {
int value();
}
@Version(2) // equivalent to @Version(value = 2)
// Full — multiple elements
public @interface RequestMapping {
String path();
String method() default "GET";
}
Annotation Processing
At compile time, annotation processors (extending AbstractProcessor) can generate code or validate constraints. Frameworks like Lombok and MapStruct use this mechanism.
For most application developers, runtime reflection on @Retention(RUNTIME) annotations is the most common use case.
Best Practices
- Use annotations to declare intent, not to replace good design
- Prefer well-known framework annotations over inventing your own when possible
- Always specify
@Retentionand@Targetexplicitly on custom annotations - Keep annotation elements simple — use primitives, String, Class, enums, or arrays of these