SwadhyAI Notes

All Java notes

Java Annotations

›Short versionOpen if you only have a minute.
  • An annotation is metadata. It does nothing until a reader acts on it: the compiler, an annotation processor, or reflection.
  • @Override is SOURCE. A typo that fails to override becomes a compile error. The annotation is gone from the class file.
  • The default retention is CLASS: present in bytecode, invisible to getAnnotation.
  • RUNTIME is what frameworks read. Forget it, and method.getAnnotation returns null. The marker was never "on" by itself.
  • @Target restricts where the annotation may be written. Members of an @interface are methods, with optional defaults.
  • @SuppressWarnings silences a compiler warning. It does not change what the program does at runtime.

An is a note on the program, written for a tool. The compiler reads some notes while it typechecks. A processor reads some during the build and generates code. A framework reads others later, through reflection, and wires a route or a column. The note and the reader are different things. is the choice of reader.

Notes the compiler already understands

@Override is . Its whole job is to make a failed override loud. Without it, a mistyped name compiles as a brand-new method, and the parent method keeps running. With it, that mistype is an error. @Deprecated marks an element callers should leave behind. The compiler warns at the call. @SuppressWarnings tells the compiler to drop a named warning at this element. The warning was a message to you. Suppressing it does not repair the code, and it does not travel into reflection. @FunctionalInterface asks the compiler to reject a second abstract method, which is the condition a lambda needs.

java
class Parent {
    public void display() {
        System.out.println("Parent");
    }
}

class Child extends Parent {
    @Override
    public void display() {
        System.out.println("Child");
    }

    @Deprecated
    public void oldMethod() {
        System.out.println("obsolete");
    }
}

public class Main {
    @SuppressWarnings("deprecation")
    public static void main(String[] args) {
        Child child = new Child();
        child.display();
        child.oldMethod();
    }
}

@SuppressWarnings("deprecation") applies to main, so the call to oldMethod compiles quietly. Delete the suppress annotation and the call becomes a warning. The deprecated method still runs either way. Retention of @Override and @SuppressWarnings is source, so a later reflective scan will not find them. They already did their work inside javac.

Declaring one, and deciding who may read it

@interface declares an annotation type. Its members look like methods. A member may have a default. The legal return types are primitives, String, Class, an enum, an annotation, and arrays of those. says where it may be placed. says reflection may read it later. Leave retention off and you get : the class file has the annotation, and getAnnotation does not.

java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Audited {
    String action();
}

class AccountService {
    @Audited(action = "withdraw")
    public void withdraw() {
    }
}

Method method = AccountService.class.getMethod("withdraw");
Audited audited = method.getAnnotation(Audited.class);
String action = audited.action(); // "withdraw"

Three kinds of reader sit on this timeline. The compiler reads source annotations and the few language annotations it knows by name. An annotation processor, running inside the compilation, can read source and class retention and can generate types or fail the build. Reflection reads only runtime retention, which is how a framework discovers withdraw and records the action string. The annotation did not audit anything. The code that called getAnnotation did, and only because the policy left the data where that call looks.

You annotate a service method so a framework will expose it as a route. The class file contains the annotation, and method.getAnnotation still returns null. Which retention is in force, and who can see the annotation?

Retention is CLASS, the default when @Retention is absent. Bytecode tools can still read the annotation from the class file. Reflection cannot, so a framework that discovers routes with getAnnotation walks past the method. Set the policy to RUNTIME if that framework is the reader. Leave it at SOURCE only when the reader is the compiler or a processor that runs before the class file is written.