Annotations in Java
An annotation attaches metadata to code. It changes nothing by itself; a compiler, a tool or a framework reads it and acts.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
Definition
An annotation is metadata attached to a declaration. It does not change behaviour on its own. Something else, the compiler, a build tool or a library at runtime, reads it and decides what to do.
public class Report {
@Override
public String toString() {
return "Report";
}
@Deprecated(since = "2.0", forRemoval = true)
public void oldExport() { }
@SuppressWarnings("unchecked")
public void legacy(List raw) { }
}The built in annotations
| Annotation | Effect |
|---|---|
@Override | Compile error if nothing is actually overridden |
@Deprecated | Warns callers; forRemoval makes the intent explicit |
@SuppressWarnings | Silences named compiler warnings in the smallest possible scope |
@FunctionalInterface | Compile error if the interface does not have exactly one abstract method |
@SafeVarargs | Asserts that a generic varargs method does not pollute the heap |
@Override is the one to use without exception. It costs nothing and turns a silent behavioural bug, a mistyped method name, into a compile error.Declaring your own
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Timed {
String label() default "";
boolean warnOnSlow() default true;
}public class SearchService {
@Timed(label = "note-search")
public List<Note> search(String term) {
return List.of();
}
}An annotation type is declared with @interface. Its members look like methods and act as named parameters; a member called value may be supplied without a name.
Meta annotations
| Meta annotation | Controls |
|---|---|
@Retention | How long it survives: SOURCE, CLASS or RUNTIME |
@Target | Where it may be placed: type, method, field, parameter and so on |
@Inherited | Whether subclasses inherit it, for class level annotations |
@Documented | Whether it appears in generated documentation |
@Repeatable | Whether it may appear more than once on one element |
Retention decides what can read it
| Policy | In the class file | Readable at runtime | Example |
|---|---|---|---|
SOURCE | No | No | @Override |
CLASS | Yes | No | Bytecode tools |
RUNTIME | Yes | Yes | Anything using reflection |
If a framework must see your annotation while the program runs, it has to be RUNTIME. This is the setting people most often get wrong.
Reading annotations with reflection
for (Method method : SearchService.class.getDeclaredMethods()) {
Timed timed = method.getAnnotation(Timed.class);
if (timed != null) {
long start = System.nanoTime();
// invoke the method here
long tookMs = (System.nanoTime() - start) / 1_000_000;
System.out.println(timed.label() + " took " + tookMs + " ms");
}
}This is the whole mechanism behind annotation driven libraries: find the annotated elements, read the values, and behave accordingly.
Repeatable annotations
@Repeatable(Roles.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Role {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Roles {
Role[] value();
}@Role("admin")
@Role("editor")
public void publish() { }Allowed member types
An annotation member may be a primitive, String, Class, an enum, another annotation, or a one dimensional array of those. Arbitrary object types are not permitted, because the values must be constants recorded in the class file.
Common mistakes
- Declaring
SOURCEretention and then trying to read the annotation at runtime. - Omitting
@Target, which allows the annotation almost anywhere. - Applying
@SuppressWarningsto a whole class and hiding genuine problems. - Expecting an annotation to do something by itself. Without a processor it is inert.
- Forgetting that
@Inheritedapplies to classes only, not to methods or interfaces.
Best practices
- Always use
@Override. - Keep
@SuppressWarningson the narrowest element possible, with a comment explaining why. - Give custom annotations an explicit
@Targetand@Retention. - Provide sensible
defaultvalues so most uses stay short. - Use
@Deprecatedtogether with a Javadoc note pointing at the replacement.
Practice
- Write an annotation
@Retry(times = 3)valid only on methods and readable at runtime. - Why does an annotation with
SOURCEretention returnnullfromgetAnnotation? - Use reflection to list every method of a class carrying your annotation.
- Explain what
@FunctionalInterfaceprevents, with an example that fails to compile. - Make an annotation repeatable and show the container type it needs.
Conclusion
Annotations record intent in a form that tools can read. Choose @Target and @Retention deliberately, remember that something must process them, and use the built in ones consistently.