Skip to content

Annotation

Introduction

In Java, an annotation is a form of syntactical metadata that can be added to the Java source code. It provides data on a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they are annotating. Classes, methods, variables, parameters and packages may be annotated. Java annotations start with the @ character and are placed before the program element to which they refer, usually before the other modifiers (public, static, final, abstract, void, ...).

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 type or declaration. They may appear in a place where they may apply to both a type or a declaration. What exactly an annotation applies to is governed by the @Target "meta-annotation." Annotations are used for a variety of purposes. Frameworks such as Spring Framework use annotations to define where the Dependencies should be injected or where the requests should be routed. Other frameworks use annotations for the generation of code. Lombok and JPA are prime examples that use annotations to generate Java (and SQL) codes.

There are three annotation categories:

  1. Marker Annotation
    • No parameter is passed.
    • The annotation is used for marking.
    • Examples: @Deprecated, @Override
  2. Single Member Annotation
    • An annotation with only one parameter.
    • This parameter has the parameter name "value", which does not need to be specified when annotating.
    • If a default value is defined for the parameter, the annotation can also be used without parameter.
    • The one parameter can be an array of several parameters.
    • Examples: @Retention, @Target
  3. Normal Annotation
    • An annotation with possibly several parameters.
    • The parameters must be preceded by the parameter name. All parameters, for which the declaration does not provide a default value, must be set.
    • Examples: @MyNewAnnotation, @Resource

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 of annotations are defined with the use of default keyword.

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

Meta-annotations are annotations that can be applied to annotation types. Special predefined meta-annotation define how annotation types can be used. @Target and @Retention are very important meta-annotations that can only be applied to annotations:

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 
}

In other words, @Target controls which program elements can be annotated. For this one or more of the following values are passed:

ElementType target example usage on target element
ANNOTATION_TYPE annotation types @Retention(RetentionPolicy.RUNTIME) interface MyAnnotation
CONSTRUCTOR constructors @MyAnnotationlic MyClass() {}
FIELD fields, enum constants @XmlAttributevate int count;
LOCAL_VARIABLE variable declarations inside methods for (@LoopVariable int i = 0; i < 100; i++)
PACKAGE package (in packageinfo.java) @Deprecatedkage very.old;
METHOD methods @XmlElementlic int getCount()
PARAMETER method/constructor parameters public Rectangle( @NamedArg("width") double width, @NamedArg("height") double height)
TYPE classes, interfaces, enums @XmlRootElementlic class Report {}

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

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 for @Retention come in the following table:

RetentionPolicy Effect
CLASS The annotation is available in the .class file, but not at runtime
RUNTIME The annotation is available at runtime and can be accessed via reflection
SOURCE The 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 Meta-Annotation is used to mark annotations whose usage should be documented by API document generators such as javadoc. It doesn't have any values. With @Documented, all classes using an annotation will list it on their generated documentation page. Without @Documented, it is not possible to see which classes are using the annotation in the documentation.

@Inherited Meta-Annotation is relevant to annotations that apply to classes. It doesn't have any values. Marking an annotation as @Inherited alters the way annotation querying works. In the case of a non-herited annotation, the query only examines the class being examined. In the case of an inherited annotation, the query will also check the super-class chain (recursively) until an instance of annotation is found. Note that only the superclasses are searched: any annotations attached to the interfaces in the hierarchy of classes will be ignored.

@Repeatable meta-annotation has been added to Java 8. It indicates that multiple annotation instances can be attached to the target annotation. This meta-annotation does not have any values.

Declare, apply and read

Usually existing annotations are applied to your own code. Before the annotations must be declared and afterwards they must be evaluated. Although the latter two activities are rarely (or never) explicitly programmed by the developer, in the following all three steps are described briefly, because this is the easiest way to understand the function.

For the declaration of an annotation the keyword @interface is used and the parameters are declared as parameterless methods.

The example declares a "Normal Annotation", i.e. an annotation with several parameters.

To make the annotation available not only at build time but also later at runtime, a meta annotation is applied to the new annotation, namely @Retention(RetentionPolicy.RUNTIME):

@Retention( RetentionPolicy.RUNTIME )
public @interface MyNewAnnotation {
    int      myIntParameter();
    String   myStringParameter();
    String   myDefaultParameter() default "xyz";
    String[] myArrayParameter();
}

In the following mini sample class the annotation is applied. The default parameter need not be set, but the absence of another parameter would lead to a compiler error. Please note the syntax with curly brackets for the array parameter (myArrayParameter = { ..., ... }).

public class ApplicationWithAnnotation {
    @MyNewAnnotation( myIntParameter=42,
                    myStringParameter="Blubb",
                    myArrayParameter={"a", "b", "c"} )
    public void myMethodWithAnnotation() {
        //...
    }
}
The following application reads the passed parameters of the annotation at runtime (including the default parameter):
import java.lang.reflect.method;
public class AnnotationUseRuntime {
    public static void main( String[] args ) throws exception {
        Method[] methods = ApplicationWithAnnotation.class.getMethods();
        for( Method m : methods ) {
            if( m.isAnnotationPresent( MyNewAnnotation.class ) ) {
                MyNewAnnotation a = m.getAnnotation( MyNewAnnotation.class );
                System.out.println( "Method: " + m.getName() );
                System.out.println( "myIntParameter(): " + a.myIntParameter() );
                System.out.println( "myStringParameter(): " + a.myStringParameter() );
                System.out.println( "myDefaultParameter(): " + a.myDefaultParameter() );
                System.out.println( "myArrayParameter()[0]: " + a.myArrayParameter()[0] );
                System.out.println( "myArrayParameter()[1]: " + a.myArrayParameter()[1] );
                System.out.println( "myArrayParameter()[2]: " + a.myArrayParameter()[2] );
            }
        }
    }
}

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.

@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.

@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.

@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.

@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. 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)

Package javax.persistenceconsists of the following annotations:

Annotation Description
@Basic data for a field, e.g. EAGER / LAZY.
@Column Data for a field, e.g. explicit column name, maximum length, Nullable (--> example).
@DiscriminatorColumn Discriminator column, if InheritanceType = SINGLE_TABLE or JOINED (see also Inheritance/Polymorphism).
@Entity Specifies a class as entity bean (--> example, other example).
@GeneratedValue Specifies a primary key generator (--> Example, other example).
@Id Specifies the primary key (--> Example, another example).
@Inheritance Specifies the mapping strategy of a class hierarchy to a set of database tables.
@Lob Large Object (BLOB or CLOB).
@ManyToMany Defines an M:N relation.
@ManyToOne Defines a N:1 relation.
@OneToMany Defines a 1:N relation.
@OneToOne Defines a 1:1 relation.
@OrderBy Sorting.
@PersistenceContext Includes an EntityManager.
@PersistenceUnit Includes an EntityManagerFactory (--> example).
@SequenceGenerator Parameterizes the primary key generator on sequence basis.
@Table Defines the table name for the Entity Bean (--> example).
@Transient Defines fields that are not to be persisted.
@UniqueConstraint Defines a uniqueness condition for a column.
@Version Version column for Optimistic Locking.

## Annotation processor

The processing of source-level annotations appeared first in Java 5. It is a useful technique during the compilation stage to generate additional source files.

The source files need not be Java files. Based on annotations in your source code, you can generate any description, metadata, documentation, resources, or any other type of files.

In many readily available Java libraries, for instance, annotation processing is actively used to generate metaclasses in QueryDSL and JPA, and to increase classes with boilerplate code in the Lombok library.

The drawback of the annotation processing API is that it can only be used to generate new files, not to modify existing ones.

The processing of the annotations takes place in multiple rounds. Each round begins with the compiler searching for the annotations in the source files and selecting the suitable annotation processors for those annotations. In turn, each annotation processor is invoked on the appropriate sources.

If any files are generated during this process, the generated files will start another round as its input. This process goes on until no new files are created during the processing phase.

Annotation processors must implement the javax.annotation.processing.Processor interface; in most cases it is recommended to extend the javax.annotation.processing.AbstractProcessor class, as it contains useful auxiliary methods.

@Target({ElementType.TYPE})
public @interface Log {}
The Target annotation with the parameter ElementType.TYPE from our Log annotation specifies that we can use @Log with all Java types (classes, interfaces, or enums). Whenever Javac finds this annotation, we want a message to be displayed on the console that shows us which class the annotation uses.

@SupportedAnnotationTypes("com.cstopics.annotationprocessor.log.Log")
public class LogProcessor extends AbstractProcessor {
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    for ( TypeElement annotation : annotations ) {
      for ( Element element : roundEnv.getElementsAnnotatedWith(annotation) ) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "found @Log at " + element);
      }
    }
    return true;
  }
}
The SupportedAnnotationTypes annotation determines the annotations that the processor is developed for. If we use “*”; the processor is called for every annotation that is found. The SupportedSourceVersion annotation determines the latest Java version the annotation processor supports. If the annotation processor is used with a newer version of Java, a warning will be shown informing the user that the processor does not support this version of Java. We then have to implement the process method of the AbstractProcessor. Two values are passed to the method:

  • A set of java.lang.model.element.TypeElement, which contains all annotations found.
  • javax.annotation.processing.RoundEnvironment, with this object, it is possible to inspect the annotated elements that have been found.

If the process method returns true, no additional annotation processors will be invoked for the annotation found. If it returns false, additional processors for the annotation may be notified.

By expanding the AbstractProcessor, you can access the javax.annotation.processing and ProcessingEnvironment that allows to access the compiler environment, such as aborting a construct process or showing a message on the console.

For the example above, the following is:

  1. We first iterate using the set of annotations found: for ( TypeElement annotation : annotations ) {
  2. Using RoundEnvironment, we then search for the elements that have been annotated with this annotation: for ( Element element : roundEnv.getElementsAnnotatedWith(annotation) ) {
  3. Next, a log of all elements found with ProcessingEnvironment is displayed on the console: processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "found @Log at " + element);

Exercise

  • Exercise 1:

    • Write an annotation that can be added to methods working at runtime and add the name and adress to that.
  • Exercise 2:

    • Write a @Inherited annotation and a not @Inherited annotation
    • Write three class A and B and C with A as UnInherited and B as Inherited and C without annotation.
    • Check if the inheritance is working