Annotations
In short: Metadata written as @Name in front of a class, method, or variable — doesn’t directly affect program logic, but provides additional information for the compiler, tools, or frameworks.
In more detail: Well-known built-in annotations: @Override (has the compiler check that an inherited method is actually being overridden — typos in the method name are caught immediately this way) and @Deprecated (marks outdated elements with a compiler warning when used). Frameworks like Spring make extensive use of their own annotations, to store configuration directly in the code instead of in separate files.
In Depth
Annotations themselves can also be custom, user-defined ones — for this, your own annotation type is defined with @interface, usually together with a @Retention meta-annotation that determines how long the annotation stays “visible”:
@Retention(RetentionPolicy.RUNTIME) // also retrievable at runtime via reflection
@Target(ElementType.METHOD) // only applicable to methods
@interface Testable {
String description() default "";
}
class Calculator {
@Testable(description = "checks addition")
int add(int a, int b) { return a + b; }
}RetentionPolicy.SOURCE (only relevant to the compiler, e.g. @Override), CLASS (ends up in the .class file, but no longer retrievable at runtime), and RUNTIME (readable at runtime via reflection) are the three levels. Frameworks like Spring or JUnit use exactly this RUNTIME mechanism to automatically find and execute methods marked @Test, for example, without having to register them manually — the annotation itself doesn’t do anything, the reading framework decides what it means.
Reflection as the counterpart
For a RUNTIME annotation to have any effect at all, some code has to read it at runtime via reflection:
Method m = Calculator.class.getMethod("add", int.class, int.class);
if (m.isAnnotationPresent(Testable.class)) {
Testable t = m.getAnnotation(Testable.class);
System.out.println("Test: " + t.description());
}This exact pattern — searching methods for an annotation and then invoking them via reflection — is behind practically every test runner and every dependency injection framework in Java.
Built-in standard annotations at a glance
Alongside @Override and @Deprecated, the most important built-in annotations include: @SuppressWarnings("unchecked") (specifically suppresses a compiler warning, e.g. for unavoidable generics type casts), @FunctionalInterface (has the compiler check that an interface really has exactly one abstract method — a prerequisite for lambda expressions), and @SafeVarargs (confirms that a varargs method with generics doesn’t cause heap pollution).
Common pitfall
Trying to read an annotation WITHOUT the matching @Retention(RUNTIME) via reflection fails silently — getAnnotation() then simply returns null, with no error message. This is a common cause of “my annotation is being ignored” bugs in hand-rolled frameworks.
Annotations on different targets
@Target determines where a custom annotation is even allowed — ElementType.TYPE (classes/interfaces), METHOD, FIELD, PARAMETER, or CONSTRUCTOR are the most common. Several targets can be given as an array:
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface Important {}Without @Target, the annotation may be used anywhere — usually unintended, since the compiler can then no longer help catch misuse early (e.g. an annotation actually meant only for methods being used on a class).
Comparison to interfaces and markers
Before Java 5 (which introduced annotations), similar purposes were often solved via so-called “marker interfaces” — empty interfaces like Serializable, which a class implements only to signal a property, without providing any actual methods. Annotations solve the same problem more elegantly: they can carry parameters (@Testable(description = "...")), can be applied multiple times to different elements of the same class, and don’t pollute the class’s own inheritance hierarchy. Serializable still exists as a marker interface today for compatibility reasons, but new APIs almost always rely on annotations.