Annotations in Java

In Java, an annotation is a form of syntactic metadata that can be added to Java source code. It provides data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate. Classes, methods, variables, parameters and packages are allowed to be annotated.

The idea behind Annotations

The Java Language Specification describes Annotations as follows:

An annotation is a marker which associates information with a program construct, but has no effect at run time.

Annotations may appear before types or declarations. It is possible for them to appear in a place where they could apply to both a type or a declaration. What exactly an annotation applies to is governed by the “meta-annotation” @Target. See “Defining annotation types” for more information.

Annotations are used for a multitude of purposes. Frameworks like Spring and Spring-MVC make use of annotations to define where Dependencies should be injected or where requests should be routed.

Related Article: Java Annotations Tutorial – Types of Java Annotations List For Beginners

Other frameworks use annotations for code-generation. Lombok and JPA are prime examples, that use annotations to generate Java (and SQL) code.

This topic aims to provide a comprehensive overview of:

How to define your own Annotations?
What Annotations does the Java Language provide?
How are Annotations used in practice?

Defining annotation types

Annotation types are defined with @interface. Parameters are defined similar to methods of a regular interface.

@interface MyAnnotation {
     String param1();
     boolean param2();
     int[] param3(); // array parameter
}
Default values
@interface MyAnnotation {
     String param1() default "someValue";
     boolean param2() default true;
     int[] param3() default {};
}

Meta-Annotations

Meta-annotations are annotations that can be applied to annotation types. Special predefined meta-annotation define how annotation types can be used.

@Target

The @Target meta-annotation restricts the types the annotation can be applied to.

@Target(ElementType.METHOD)
@interface MyAnnotation {
// this annotation can only be applied to methods
}

Multiple values can be added using array notation, e.g. @Target({ElementType.FIELD, ElementType.TYPE})

Available Values Element Typetargetexample usage on target element
ANNOTATION_TYPEannotation types@Retention(RetentionPolicy.RUNTIME) Interface MyAnnotation
CONSTRUCTORconstructors@MyAnnotationlic MyClass() {}
FIELDfields, enum constants@XmlAttributevate int count;
LOCAL_VARIABLEvariable declarations inside
methods
for (@LoopVariable int i = 0; i < 100; i++) { @Unused
String resultVariable; }
PACKAGEpackage (in packageinfo.
java)
@Deprecatedkage very.old;
METHODmethods@XmlElementlic int getCount() {…}
PARAMETERmethod/constructor
parameters
public Rectangle( @NamedArg(“width”) double width,
@NamedArg(“height”) double height) {

}
TYPEclasses, interfaces, enums@XmlRootElementlic class Report {}

Version ≥ Java SE 8

ElementTypetargetexample usage on target element
TYPE_PARAMETERType parameter declarationspublic <@MyAnnotation T> void f(T t) {}
TYPE_USEUse of a typeObject o = “42”;ing s = (@MyAnnotation String) o;

@Retention

The @Retention meta-annotation defines the annotation visibility during the applications compilation process or execution. By default, annotations are included in .class files, but are not visible at runtime. To make an
annotation accessible at runtime, RetentionPolicy.RUNTIME has to be set on that annotation.

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
     // this annotation can be accessed with reflections at runtime
}

Available values

RetentionPolicyEffect
CLASSThe annotation is available in the .class file, but not at runtime
RUNTIMEThe annotation is available at runtime and can be accessed via reflection
SOURCEThe annotation is available at compile time, but not added to the .class files. The annotation can
be used e.g. by an annotation processor.

@Documented

The @Documented meta-annotation is used to mark annotations whose usage should be documented by API documentation generators like javadoc. It has no values. With @Documented, all classes that use the annotation will
list it on their generated documentation page. Without @Documented, it’s not possible to see which classes use the annotation in the documentation.

@Inherited

The @Inherited meta-annotation is relevant to annotations that are applied to classes. It has no values. Marking an annotation as @Inherited alters the way that annotation querying works.

For a non-inherited annotation, the query only examines the class being examined. For an inherited annotation, the query will also check the super-class chain (recursively) until an instance of the annotation is found.

Note that only the super-classes are queried: any annotations attached to interfaces in the classes hierarchy will be ignored.

@Repeatable

The @Repeatable meta-annotation was added in Java 8. It indicates that multiple instances of the annotation can be attached to the annotation’s target. This meta-annotation has no values.

The @Repeatable meta-annotation was added in Java 8. It indicates that multiple instances of the annotation can be attached to the annotation’s target. This meta-annotation has no values.

Runtime annotation checks via reflection

Java’s Reflection API allows the programmer to perform various checks and operations on class fields, methods and annotations during runtime. However, in order for an annotation to be at all visible at runtime, the RetentionPolicy must be changed to RUNTIME, as demonstrated in the example below:

@interface MyDefaultAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@interface MyRuntimeVisibleAnnotation {
}
public class AnnotationAtRuntimeTest {
      @MyDefaultAnnotation
      static class RuntimeCheck1 {
      }

      @MyRuntimeVisibleAnnotation
      static class RuntimeCheck2 {
      }
 
      public static void main(String[] args) {
            Annotation[] annotationsByType =    RuntimeCheck1.class.getAnnotations();
            Annotation[] annotationsByType2 = RuntimeCheck2.class.getAnnotations();

           System.out.println("default retention: " + Arrays.toString(annotationsByType));
           System.out.println("runtime retention: " + Arrays.toString(annotationsByType2));
      }
}

Built-in annotations

The Standard Edition of Java comes with some annotations predefined. You do not need to define them by yourself and you can use them immediately. They allow the compiler to enable some fundamental checking of methods,
classes and code.

@Override

This annotation applies to a method and says that this method must override a superclass’ method or implement an abstract superclass’ method definition. If this annotation is used with any other kind of method, the compiler will throw an error.

Concrete superclass

public class Vehicle {
     public void drive() {
         System.out.println("I am driving");
     }
}

class Car extends Vehicle {
      // Fine
     @Override
     public void drive() {
           System.out.prinln("Brrrm, brrm");
     }
}

Abstract class

abstract class Animal {
      public abstract void makeNoise();
}
class Dog extends Animal {
     // Fine
     @Override
     public void makeNoise() {
         System.out.prinln("Woof");
     }
}

Does not work

class Logger1 {
      public void log(String logString) {
           System.out.prinln(logString);
      }
}

class Logger2 {
       // This will throw compile-time error. Logger2 is not a subclass of Logger1.
      // log method is not overriding anything
      @Override
      public void log(String logString) {
            System.out.println("Log 2" + logString);
      }
}

The main purpose is to catch mistyping, where you think you are overriding a method, but are actually defining a new one.

class Vehicle {
    public void drive() {
         System.out.println("I am driving");
    }
}

class Car extends Vehicle {
      // Compiler error. "dirve" is not the correct method name to override.
     @Override
     public void dirve() {
           System.out.prinln("Brrrm, brrm");
     }
}

Note that the meaning of @Override has changed over time:

  • In Java 5, it meant that the annotated method had to override a non-abstract method declared in the superclass chain.
  • From Java 6 onward, it is also satisfied if the annotated method implements an abstract method declared in the classes superclass / interface hierarchy.

(This can occasionally cause problems when back-porting code to Java 5.)

@Deprecated

This marks the method as deprecated. There can be several reasons for this:

  • the API is flawed and is impractical to fix,
  • usage of the API is likely to lead to errors,
  • the API has been superseded by another API,
  • the API is obsolete,
  • the API is experimental and is subject to incompatible changes,
  • or any combination of the above.

The specific reason for deprecation can usually be found in the documentation of the API.

The annotation will cause the compiler to emit an error if you use it. IDEs may also highlight this method somehow as deprecated.

class ComplexAlgorithm {
@Deprecated
public void oldSlowUnthreadSafeMethod() {
// stuff here
}
public void quickThreadSafeMethod() {
// client code should use this instead
}
}

@SuppressWarnings

In almost all cases, when the compiler emits a warning, the most appropriate action is to fix the cause. In some instances (Generics code using untype-safe pre-generics code, for example) this may not be possible and it’s better to suppress those warnings that you expect and cannot fix, so you can more clearly see unexpected warnings.

This annotation can be applied to a whole class, method or line. It takes the category of warning as a parameter.

@SuppressWarnings("deprecation")
public class RiddledWithWarnings {
// several methods calling deprecated code here
}

@SuppressWarning("finally")
public boolean checkData() {
// method calling return from within finally block
}

It is better to limit the scope of the annotation as much as possible, to prevent unexpected warnings also being suppressed. For example, confining the scope of the annotation to a single-line:

ComplexAlgorithm algorithm = new ComplexAlgorithm();
@SuppressWarnings("deprecation") algoritm.slowUnthreadSafeMethod();
// we marked this method deprecated in an example above

@SuppressWarnings("unsafe") List list = getUntypeSafeList();
// old library returns, non-generic List containing only integers

The warnings supported by this annotation may vary from compiler to compiler. Only the unchecked and deprecation warnings are specifically mentioned in the JLS. Unrecognized warning types will be ignored.

@SafeVarargs

Because of type erasure, void method(T… t) will be converted to void method(Object[] t) meaning that the compiler is not always able to verify that the use of varargs is type-safe. For instance:

private static void generatesVarargsWarning(T… lists) {

There are instances where the use is safe, in which case you can annotate the method with the SafeVarargs annotation to suppress the warning. This obviously hides the warning if your use is unsafe too.

@FunctionalInterface

This is an optional annotation used to mark a FunctionalInterface. It will cause the compiler to complain if it does not conform to the FunctionalInterface spec (has a single abstract method)

@FunctionalInterface
public interface ITrade {
    public boolean check(Trade t);
}

@FunctionalInterface
public interface Predicate {
    boolean test(T t);
}

Leave a Comment