Annotations
An annotation is a marker in code that starts with @: @Override, @Test, @Deprecated. On its own it does nothing — it's extra information (metadata) for the compiler, build tools and libraries. For example, the JUnit library finds test methods by the @Test annotation, and Spring configures an application through its own annotations.
@Override
The most common annotation is @Override. It tells the compiler: "this method overrides a method of the parent class or interface". If that isn't true, the compiler reports an error:
class Animal {
String sound() {
return "...";
}
}
class Cat extends Animal {
@Override
String sound() {
return "Meow";
}
}
void main() {
Animal pet = new Cat();
IO.println(pet.sound());
}Output:
MeowThe code works without @Override too. But the annotation protects against typos: if you write @Override String sounds(), the program won't compile.
class Animal {
String sound() {
return "...";
}
}
class Cat extends Animal {
@Override
String sounds() {
return "Meow";
}
}
void main() {
IO.println(new Cat().sound());
}Output:
Main.java:8: error: method does not override or implement a method from a supertype
@Override
^
1 error
error: compilation failedOther built-in annotations
@Deprecated— the element is outdated and shouldn't be used. The compiler warns everyone who calls it.@SuppressWarnings("unchecked")— asks the compiler not to print a particular warning.@FunctionalInterface— the interface must have exactly one abstract method so that a lambda can implement it. Add a second one and the compiler reports an error.
@FunctionalInterface
interface Discount {
double apply(double price);
}
void main() {
Discount blackFriday = price -> price * 0.7;
IO.println(blackFriday.apply(100));
}Output:
70.0Your own annotation
An annotation is declared with @interface. Inside you list its elements — they are written like methods without parameters and can have default values:
// Command.java
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Command {
String name();
String description() default "no description";
}The two annotations above the declaration are meta-annotations, that is, annotations for annotations:
@Retentionsets how long the annotation is kept.RUNTIMEmeans it's available while the program runs. Without it only the compiler sees it.@Targetsets what can be annotated:METHOD— only methods,TYPE— classes,FIELD— fields.
Reading annotations
Annotations with RUNTIME can be read through reflection — a mechanism that lets a program examine its own classes and methods. It's covered in detail in the next lesson. Let's find all command methods and print their descriptions:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Command {
String name();
String description() default "no description";
}
class Bot {
@Command(name = "/start", description = "greeting")
void start() {}
@Command(name = "/help")
void help() {}
void internal() {}
}
void main() {
List<Method> methods = new ArrayList<>(List.of(Bot.class.getDeclaredMethods()));
methods.sort(Comparator.comparing(Method::getName));
for (Method method : methods) {
Command command = method.getAnnotation(Command.class);
if (command != null) {
IO.println(command.name() + " — " + command.description());
}
}
}Output:
/help — no description
/start — greetingThe internal method isn't annotated, and getAnnotation returned null for it. The order of methods from getDeclaredMethods isn't guaranteed, so the list is sorted by name. This is exactly how frameworks find command handlers, URL routes and tests.
Common mistake
If you forget @Retention(RetentionPolicy.RUNTIME), by default the annotation doesn't make it into the running program, and reflection won't see it:
@interface Command {
String name();
}
class Bot {
@Command(name = "/start")
void start() {}
}
void main() throws NoSuchMethodException {
Method method = Bot.class.getDeclaredMethod("start");
IO.println(method.isAnnotationPresent(Command.class));
}Output:
falseThere's no error, the annotation just "disappeared". For annotations read at run time, always specify RUNTIME.