Java Annotations and Reflection. Create, Parse and Process Custom Annotations.

What are Java Annotations?

Java annotations are tags that we insert into source code for providing more information about the code. They associate information with the annotated program element. Beside Java annotations Java programs have copious amounts of informal documentation that is typically contained within comments in the source code file but annotations are different from comments they annotate the program elements directly using annotation types to describe the form of the annotations. Annotations present the information in a standard and structured way so that it could be used amenably by processing tools.

A Java annotation is a modifier starts with @ symbol as Javadoc tags do, followed by annotation-tag name followed by zero or more element-value pairs enclosed in parenthesis. There may be white space between @ symbol and the annotation-tag name but standard coding practices advice to avoid any space between the two. Annotations are processed by annotation processing tools [use reflection] to extract the provided information and to use that information for intended purpose. That's why Java annotations are called metadata about the code.

Java annotations can be extracted at the time of compiling the code by the compiler or at run time by annotation processors. However, Java compiler can understand only a few Java annotations and further to Java compiler we do need annotation processing tools either developed by ourselves or third party annotation processors to parse Java annotations.

Purpose of Annotations - Adding Metadata to Java Program

The purpose of an annotation is to associate information with the annotated program element. An annotations may be used as modifiers in any declaration, whether package, class (including enums), interface (including annotation types), field, method, formal parameter, constructor, or local variable.

Annotations may also be used on enum constants. Such annotations are placed immediately before the enum constant they annotate. Java annotations are conventionally placed before all other modifiers, but this is not a compulsion; they may be freely intermixed with other modifiers.

However, you maybe familiar with Javadoc tags that embed metadata in Java source code but unlike Javadoc tags, Java annotations can be reflective and retrievable at run-time.

One of the main reasons for adding annotations to the Java platform is to enable development and runtime tools to have a common infrastructure in order to reduce the effort required for development and deployment. By having a common infrastructure metadata structure can be standardized, thus a tool could use the metadata information in form of annotations to generate additional source code, or to provide additional information for debugging, or other purposes.

Annotations to Java have been introduced first time in Java 1.5. Prior to Java 1.5, it was common to use naming patterns to indicate that some program elements demanded special treatment by a tool or framework. Naming patterns have disadvantages; they are not efficient to cope up with typographical errors. Second, there is no way to ensure that naming patterns are used only on appropriate program elements. Third disadvantage of naming patterns is that they provide no good way to associate parameter values with program elements. Java annotations solve all of these problems nicely. Java annotations have been covered in section 9.6 and 9.7 in Java Language Specification.

Built-in and Custom Annotation Types

Java defines seven built-in annotations out of which three (@Override, @Deprecated, and @SuppressWarnings) are applied to Java code and they are included in java.lang library. These three annotations are called regular Java annotations. Rest four (@Retention, @Documented, @Target, and @Inherited) are applied to other annotations and they are included in java.lang.annotation library. These annotations are called meta Java annotations. Meta Java annotations are used to create custom annotations. Custom annotations will be discussed shortly.

Following table shows the functionality of built-in annotations, and the library in which they are defined.

Table 1: Built-in Java Annotations
Annotation Name Applicable To Use Included in
Java Annotations Applied to Java code
@Override Member Methods Checks that this method overrides a method from its superclass java.lang
@Deprecated All annotable items Marks item as deprecated java.lang
@SuppressWarnings All annotable items except packages and annotations Suppress warning of given type java.lang
Java Annotations Applied to Other Annotations
@Retention Annotations Specifies how long this annotation is retained - whether in code only, compiled into the class, or available at run time through reflection. java.lang.annotation
@Documented Annotations Specifies that this annotation should be included in the documentation of annotated items java.lang.annotation
@Target Annotations Specifies the items to which this annotation can be applied java.lang.annotation
@Inherited Annotations Specifies that this annotation, when applied to a class, is automatically inherited by its subclasses. java.lang.annotation

Using Built-in Annotations

From the usage point of view annotations are categorized as Regular and Meta Annotations. Regular Annotations are applied to Java source code while Meta Annotations are applied to other Java annotations. Meta Annotations are also used for writing custom annotations; we will soon discuss custom annotations.

Regular Java Annotations

@Override Java Annotation

The @Override annotation applies only to methods. At the time of compilation the compiler checks that a method with @Override annotation overrides the superclass's method or not. If yes, it is OK else it reports a compile time error. The retention policy for @Override annotation is SOURCE which means this annotation will be discarded completely after compiling the code and would not be included in .class file or bytecode.

Retention policy for an annotation is the visibility of that annotation and it is of three types: CLASS, RUNTIME, and SOURCE.

  • Annotations having CLASS level visibility are to be recorded in the class file by the compiler but need not be retained by the VM at run time.
  • RUNTIME level visibility annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
  • SOURCE level visibility annotations are to be discarded by the compiler.

While compiling the following piece of code the compiler will report you an error because the equals method does not override the equals method of the Object class. Method equals() takes a parameter of type Object, not Human.

class Human
{
    @Override public boolean equals(Human otherHuman)
    {
        //comparison code here
        return false; //mock return statement
    }
}

@Deprecated Java Annotation

While programming in eclipse IDE you may have seen some strikethrough method names. These methods are deprecated and their use is not encouraged any more because they could be dropped in further releases. The @Deprecated Java annotation can be attached to any items whose use is no longer encouraged; it warns the programmer not to use this API or constant or any item that is marked deprecated. This Java annotation has the same role as the @deprecated Javadoc tag. This Java annotation follows the RUNTIME retention policy; therefore it is kept safe in bytecode and available for reflection at run time.

Note that there is a Javadoc tag also with the name @deprecated but the Javadoc tag starts with a lowercase "d" and the Java annotation starts with an uppercase "D". Both @deprecated and @Deprecated are conceptually related.

The following piece of code will successfully compile but with warning deprecatedMethod() in Human has been deprecated.

class DeprecatedDemo 
{
    public static void main(String a[])
    {
        Human h = new Human();
        h.deprecatedMethod(); //deprecated
    }
}
 
class Human
{
    @Deprecated public void deprecatedMethod()
    {
        System.out.println("It is deprecated method");
    }
}

@SuppressWarnings Java Annotation

The @SuppressWarnings Java annotation instructs compiler to suppress the warnings of a particular type that are shown during compilation. Note that the set of warnings suppressed in a given element is a superset of the warnings suppressed in all containing elements. For example, if you annotate a class to suppress one warning and annotate a method to suppress another, both warnings will be suppressed in the method.

As a matter of style, programmers should always use this Java annotation on the most deeply nested element where it is effective. If you want to suppress a warning in a particular method, you should only annotate that method rather than its class.

The retention policy for @SuppressWarnings Java annotation is also SOURCE; therefore it will be discarded after compiling the code and would not be included in .class file.

Meta Java Annotations

@Target Java Annotation

While defining a custom Java annotation we have to specify which element (class, method, field, constructor etc.) this newly defined annotation would be applicable on. The @Target annotation is used for that purpose to set the target elements on which the custom annotation can be applied.

Table 2 shows possible values of elements for @Target annotation. They belong to the enumerated type ElementType. You can specify any number of element types, enclosed in braces.

Table 2: Element Types for the @Target Annotation
Element Type Annotation Applies To
ANNOTATION_TYPE Annotation type declarations
PACKAGE Packages
TYPE Classes (including enum) and interfaces (including annotation types)
METHOD Methods
CONSTRUCTOR Constructors
FIELD Fields (including enum constants)
PARAMETER Method or constructor parameters
LOCAL_VARIABLE Local variables

The compiler checks if the annotation has been applied to a permitted type only. If not then compile-time error occurs. And so, a Java annotation without @Target restriction can be applied to any item.

@Retention Java Annotation

As name suggets, @Retention meta annotation specifies till what level an annotation will be retained. To decide the scope of the custom annotation we have to specify one of the three values (SOURCE, CLASS, or RUNTIME) of RetentionPolicy. The default is RetentionPolicy.CLASS.

RetentionPolicy.SOURCE specifies the scope of custom annotation to the compile time. Annotations having retention policy RetentionPolicy.SOURCE are not included in bytecode.

Annotations those are carrying RetentionPolicy.CLASS policy of retention are included in .class files, but the virtual machine need not to load them.

Annotations having RetentionPolicy.RUNTIME policy are included in class files and loaded by the virtual machine. Java annotations that are given RUNTIME retention policy can be accessed at run time through the reflection API.

@Documented Java Annotation

The @Documented meta annotation hints Javadoc tool to include this annotation in the documentation wherever it is used. Documented Java annotations should be treated just like other modifiers, such as protected or static, for documentation purposes. The use of other annotations is not included in the documentation.

@Inherited Java Annotation

The @Inherited annotation can be applied only to annotations for classes. When a superclass is annotated with an @Inherited Java annotation then all of its subclasses automatically have the same annotation.

Java Custom Annotation Types and Annotation Parsing Using Reflection

As of now we have discussed built-in annotations provided by Java language. Sometimes we may need to design our own customized Java annotations depending upon the nature of requirement and parse them at run time to extract the information supplied by annotations. Java annotations that we design are called custom Java annotations.

Java annotation types are a special kind of interface, declared with the interface keyword preceded by the @ symbol. Java annotation types can be declared anywhere an interface can be declared that is, as a top-level Java annotation type or nested within another type and can have the same modifiers applied as interfaces have. Characterizing Java annotation types as interfaces is a little misleading, however, as aside from borrowing some syntax and some associated usage rules, Java annotation types bear little resemblance to interfaces in normal use.

Each annotation is defined by an annotation interface, no matter it is custom or built-in Java annotation. An annotation definition may or may not have methods, if it has then the methods of interface corresponds to the elements of the annotation. For an example, we can define a simple custom @Author Java annotation as follows.

@Target({ElementType.METHOD, 
	 ElementType.CONSTRUCTOR, 
	 ElementType.TYPE, 
	 ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface Author 
{
    String author() default "";
}

annotation Author defined above has one method author() that by default returns a zero length string or the value provided by the programmer at the time of applying this annotation to supported element types. At run time a tool or your own program would call the author() method to retrieve the author element of the Author annotation.

The Target and Retention Java annotations used while declaring Author annotation are meta Java annotations. They annotate the Author annotation, marking it as a Java annotation that can be applied to methods, constructors, classes, enums, and interfaces (including annotation types). Author annotation is retained when the class file is loaded into memory by the virtual machine as its retention policy is RUNTIME.

Let's now see how to use and access the value of author element of @Author Java annotation in a program.

Before everything it must be noted that Java Annotation Retention Policy should be RUNTIME otherwise annotated information will not be accessible at runtime and we won't be able to extract any data from it.

/* AccessAnnotations.java */
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
public class AccessAnnotations
{
  public static void main(String[] args)
  {
    Class<AnnotatedClass> ac = AnnotatedClass.class;
    try
    {
      Field f = ac.getDeclaredField("annotatedField");
      Method mGetter = ac.getMethod("getAnnotatedField");
      Method mSetter = ac.getMethod("setAnnotatedField", new Class[] {int.class});
      Constructor<AnnotatedClass> cons = ac.getConstructor();
 
      System.out.println("Class Author: " + ac.getAnnotation(Author.class).author());
      System.out.println("Field Author: " + f.getAnnotation(Author.class).author());
      System.out.println("Constructor Author: " + cons.getAnnotation(Author.class).author());
      System.out.println("Getter Author: " + mGetter.getAnnotation(Author.class).author());
      System.out.println("Setter Author: " + mSetter.getAnnotation(Author.class).author());
    }
    catch (NoSuchMethodException e)
    {
      e.printStackTrace();
    }
    catch (SecurityException e)
    {
      e.printStackTrace();
    }
    catch (NoSuchFieldException e)
    {
      e.printStackTrace();
    }
  }
}
 
 
@Author (author = "Krishan Class")
class AnnotatedClass
{
    @Author (author = "Krishan Field")
    private int annotatedField;
 
    @Author (author = "Krishan Constructor")
    public AnnotatedClass()
    {
        System.out.println("Object created");
        annotatedField = 10;
    }
 
    @Author (author = "Krishan Getter")
    public int getAnnotatedField()
    {
        return annotatedField;
    }
 
    @Author (author = "Krishan Setter")
    public void setAnnotatedField(int annotatedField)
    {
        this.annotatedField = annotatedField;
    }
}
 
 
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface Author 
{
    String author () default "";
}
 
 
OUTPUT
------
Class Author: Krishan Class
Field Author: Krishan Field
Constructor Author: Krishan Constructor
Getter Author: Krishan Getter
Setter Author: Krishan Setter

You can think above Java program an annotation parser. While reading the program you may find a few things new if you have not used java.lang.reflect package of Java so far. The main method of AccessAnnotations acts like a Java annotation parsing tool. It accesses the AnnotatedClass members with help of reflection classes such as Field, Constructor, and Method etc and parses the meta information provided along with AnnotatedClass and its members.

For example, in statement Field f = ac.getDeclaredField ("annotatedField"); the class Field belongs to java.lang.reflect package and the object f of type Field provides information about, and access to the private field annotatedField of class AnnotatedClass. Then you can call getAnnotation method on f to access the annotation information attached to annotatedField. There may be more than one Java annotations attached to one field so you need to pass the Annotation type as a parameter to getAnnotation in order to get the specified type annotation if such a Java annotation is present, else null.

Last Word

In this tutorial we discussed how to write and process user defined custom annotations in Java. Java annotations add metadata to Java source code. Java annotations can be accessed at run-time by using reflection. Hope you have enjoyed reading this tutorial, please do write us if you have any suggestion/comment or come across any error on this page. Thanks for reading!

References



Share this page on WhatsApp

Get Free Tutorials by Email

About the Author

is the founder and main contributor for cs-fundamentals.com. He is a software professional (post graduated from BITS-Pilani) and loves writing technical articles on programming and data structures.