How
Java annotations
work

My credentials

  • ✅ EqualsVerifier
  • ✅ AnnotationScript
  • ⛔ “Parallel Java”

What is this magic?

@Entity
@Data
public class User {
    @Id
    private Long id;

    @NotNull
    @Valid
    private String name;

    @Autowired
    private Service service;

    @Override
    public String toString() {
        return "User: " + name;
    }
}

What is an annotation?

  • metadata for code
  • for code analysis
  • overpowered

Syntax of an annotation

@Target(ElementType.METHOD)
public @interface MyAnnotation {

    String value();
}

Allowed parameter types


  • primitive types (int, long, double, etc)
  • String, Class, Enum
  • Another annotation
  • 1-dimensional arrays of these


Must be CONSTANT

Parameter syntax

public @interface MyAnnotation {

    String value();
    int count() default 0;
}
✅ @MyAnnotation("x")
✅ @MyAnnotation(value = "x")
✅ @MyAnnotation(value = "x", count = 42)

⛔ @MyAnnotation("x", count = 42)
⛔ @MyAnnotation(count = 42)

Meta-annotations

   
@Target 🟢
@Retention 🟢
@Documented
@Repeatable
@Inherited

Optional meta-annotations

   
@Documented put it in Javadoc
@Repeatable use it multiple times
@Inherited also put it on subclasses

Targets

@Target({ ElementType.FIELD, ElementType.METHOD })

 

  • Type
  • Field
  • Method
  • Parameter
  • Constructor
  • Local variable
  • Annotation type
  • Package
  • Type parameter
  • Type use
  • Module
  • Record

Retention: runtime

@Retention(RetentionPolicy.RUNTIME)



Available through reflection

  • Spring
  • JPA

Reading RUNTIME annotations

@MyAnnotation("woohoo")
public class MyClass {}
Class<?> myClass = MyClass.class;

Annotation[] annotations = myClass.getDeclaredAnnotations();
                               // Annotation[1] = { MyAnnotation("woohoo") }

MyAnnotation ann = myClass.getAnnotation(MyAnnotation.class);
                               // MyAnnotation("woohoo")

ann.value()
                               // "woohoo"

Reading RUNTIME annotations

public class MyClass {
    @MyAnnotation("wheee")
    public String myField;
}
Class<?> myClass = MyClass.class;
Field field = myClass.getField("myField");

Annotation[] annotations = field.getDeclaredAnnotations();
                               // Annotation[1] = { MyAnnotation("wheee") }

MyAnnotation ann = field.getAnnotation(MyAnnotation.class);
                               // MyAnnotation("wheee")

ann.value()
                               // "wheee"

Retention: class

@Retention(RetentionPolicy.CLASS)



Written into classfile but unavailable

  • @NonNull
  • @Nullable

Reading CLASS annotations

  • Need a tool like Byte Buddy
  • Requires a lot more code

Retention: source

@Retention(RetentionPolicy.SOURCE)



Available only at compile-time

  • @Override
  • @Deprecated
  • but also: Lombok

Reading SOURCE annotations

  • Need to be the compiler
  • Or write a compiler plugin
  • Oh no
  • Why would you do that?
  • So much WHY

Frameworks

  • Spring
    • process at run-time
    • slow startup
  • Quarkus, Micronaut
    • process at compile-time
    • has to process files anyway

Conclusion

Annotations are like magic


  • They do a lot of work
  • They require a lot of work

Thanks!

Jan Ouwens

EqualsVerifierjqno.nl jqno

Slides at jqno.nl/talks