Skip to content

Annotations

Introduction

Annotations are a form of metadata that provides information about a program that is not part of it. Annotations do not directly affect the operation of the code they refer to.

Annotations implement 3 basic functionalities:

  • providing information for the compiler - the compiler can use the annotation to detect errors or hide warnings
  • information processing to generate code, XML files, etc. during compilation
  • the annotations are available for checking at runtime

Annotation basics

An annotation tag starts with the '@' sign which tells the compiler that it is dealing with an annotation and a name, e.g.

  • @Override

Annotations can also take arguments, e.g.

@SuppressWarnings(value = "unchecked")                        // annotation with a single argument
@LogPotencialErrors(showStackTrace = "true", format = "true") // annotation with two arguments

Note that if the only argument passed to the annotation is an argument named value, this key may be omitted. In case of using more than one argument, we must always give all their names. This behavior is explained in the following example:

@SuppressWarnings(value = "unchecked")  // use of value argument
@SuppressWarnings("unchecked")          // an entry equivalent to that in the line above

@SomeOtherAnnotation(value = "someValue", otherValue = "someOtherValue") // the case where we can NOT omit the key 'value'

NOTE: Multiple annotations can be used on one item.

Usage

We can use annotations in many places in our code. The elements where this is possible include:

  • class and interface definitions
  • class fields
  • constructors, methods and their arguments

The elements on which we can apply a given annotation depends on how it was defined.

NOTE: The order of annotations used on a given element does not matter.

Defining annotations

Annotations are defined similar to interfaces, the only difference is adding an @ sign before the keyword.

public @interface ComponentInfo {
   String name();
   String date();
   int currentRevision(); 
}

In the example above, name (), date (), and currentRevision () are not methods, but elements of the annotation. Their name most often differs from the names of common methods. There is no point in looking for verbs in them (such as get,set or do). Elements defined this way become arguments of such annotation. An example use of the annotation defined above could look like this:

@ComponentInfo(name = "someName", date = "10-12-2021", currentRevision = 3)

NOTE: The order in which the values of the arguments in the annotation are defined does not matter.

Limitations

While defining an annotation and its elements ("methods"), we must bear in mind some limitations:

  • elements can only return certain types, including:
    • simple types (e.g. int, float, double) and their object-oriented counterparts (e.g. Integer, Double)
    • String class
    • enums
    • other annotations
    • arrays of the types as mentioned above
  • these elements cannot take any arguments (i.e. look like method declarations with no arguments)

Below is another valid example of an annotation declaration, using arrays and the Month enum in its definition:

public @interface PersonInfo {
   String[] names();
   String[] dates();
   Month month(); // java.time.Month is an enumerated type
}

// sample use
@PersonInfo(names = {"Alice", "Andrew"}, dates = {}, month = Month.APRIL)

default

The annotation definitions in the previous examples required us to specify values for all items when using them. The default keyword allows you to define the default values of annotation elements, which are used at the end of the defined annotation element, e.g.

public @interface InputArgs {
  int intValue() default 2; // the default value of the intValue field if omitted is 2
  double doubleValue();     // no default value, required field
}

Use in examples

The annotations defined in the previous examples can be used in the [previously] (#use) places of the application, e.g.

@PersonInfo(names = {}, dates = "12-12-2022", month = Month.AUGUST)
public class AnnotationsExamples {

  @ComponentInfo(name = "annotationsExamples", date = "12-10-2020", currentRevision = 2)
  private String someField;


  public AnnotationsExamples(@InputArgs(doubleValue = 7) final String someField) { //intValue is set to the default
    this.someField = someField;
  }
}

Annotations built into the language

Various kinds of annotations are defined in Java SE. Some annotation types are used by the Java compiler and some change the behavior of other annotations. Some of the basic and most used are:

  • @Deprecated - it allows you to mark a method/class as obsolete and one that should no longer be used.
  • @Override - tells the compiler that the element is to replace the element declared in the parent class.
  • @SuppressWarnings - tells the compiler to suppress certain warnings it would otherwise generate.

There are also special annotations that can be used to specify other annotations, these are called meta-annotations, e.g.:

  • @Documented - auxiliary annotation that can e.g. display documentation with javadoc.
  • @Repeatable - the annotation, introduced in Java SE 8, indicates that a particular annotation can be applied more than once on the same element (e.g. a field).
  • @Inherited - the annotation indicates that the annotations used are visible during class inheritance.
  • @FunctionalInterface - annotation pointing to interface that has one abstract method.

We could use the annotations defined so far on [any] (#use) elements. When defining an annotation intended for use by other programmers, we want to indicate where and at what point such an annotation will be processed. There are two annotations for this:

  • @Retention - which specifies the time during which the annotation is visible.
  • @Target - which specifies a list of items on which the annotation can be applied.

Using the @Retention annotation we can indicate the following value of thevalue field:

  • RetentionPolicy.SOURCE - specifies that the annotation is only visible at the source code level and is ignored by the compiler.
  • RetentionPolicy.CLASS - specifies that the annotation is visible to the compiler at compile time but is ignored by the JVM.
  • RetentionPolicy.RUNTIME - specifies that the annotation is stored by the JVM so that it can be used at runtime (ie while the program is running).

In turn, to the Target annotation we have to pass a value of the ElementType type, which has many possible values, including:

wartość elements on which annotation can be applied
ElementType.CONSTRUCTOR onstructors declarations
ElementType.FIELD class fields
ElementType.METHOD method declarations
ElementType.TYPE class, interface, enum definitions
ElementType.PARAMETER method parameter

In the example below, we declare an annotation that is visible at runtime, which we can define on the class declaration and class fields:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD })
public @interface NameFilter {
}

@NameFilter                       // correct use -> ElementType.TYPE
public class NameFilterUsage {

  @NameFilter                     // correct use -> ElementType.FIELD
  private String field;

  @NameFilter                     // INCORRECT use -> no ElementType.METHOD
  public void setField(@NameFilter final String field) { // INCORRECT use -> no ElementType.PARAMETER
    this.field = field;
  }
}