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.

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

AnnotationEffect
@OverrideCompile error if nothing is actually overridden
@DeprecatedWarns callers; forRemoval makes the intent explicit
@SuppressWarningsSilences named compiler warnings in the smallest possible scope
@FunctionalInterfaceCompile error if the interface does not have exactly one abstract method
@SafeVarargsAsserts 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 annotationControls
@RetentionHow long it survives: SOURCE, CLASS or RUNTIME
@TargetWhere it may be placed: type, method, field, parameter and so on
@InheritedWhether subclasses inherit it, for class level annotations
@DocumentedWhether it appears in generated documentation
@RepeatableWhether it may appear more than once on one element

Retention decides what can read it

PolicyIn the class fileReadable at runtimeExample
SOURCENoNo@Override
CLASSYesNoBytecode tools
RUNTIMEYesYesAnything 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 SOURCE retention and then trying to read the annotation at runtime.
  • Omitting @Target, which allows the annotation almost anywhere.
  • Applying @SuppressWarnings to a whole class and hiding genuine problems.
  • Expecting an annotation to do something by itself. Without a processor it is inert.
  • Forgetting that @Inherited applies to classes only, not to methods or interfaces.

Best practices

  • Always use @Override.
  • Keep @SuppressWarnings on the narrowest element possible, with a comment explaining why.
  • Give custom annotations an explicit @Target and @Retention.
  • Provide sensible default values so most uses stay short.
  • Use @Deprecated together with a Javadoc note pointing at the replacement.

Practice

  1. Write an annotation @Retry(times = 3) valid only on methods and readable at runtime.
  2. Why does an annotation with SOURCE retention return null from getAnnotation?
  3. Use reflection to list every method of a class carrying your annotation.
  4. Explain what @FunctionalInterface prevents, with an example that fails to compile.
  5. 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.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →
Java

Enums in Java

An enum is a class with a fixed set of instances, which makes an invalid value impossible rather than merely unlikely.

Read more

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.