Learn how to use Java annotations to associate metadata with classes, methods, and other application elements in your Java programs.
There are times when you need to associate metadata, or data that describes data, with classes, methods, or other elements in your Java code. For example, your team might need to identify unfinished classes in a large application. For each unfinished class, the metadata would include the name of the developer responsible for finishing the class and the class’s expected completion date.
Before Java 5, comments were Java’s only flexible mechanism for associating metadata with application elements. But because the compiler ignores them, comments are not available at runtime. And even if they were available, you would have to parse the text to obtain crucial data items. Without standardizing how the data items were specified, they might be impossible to parse.
Java 5 changed everything by introducing annotations, a standard mechanism for associating metadata with various application elements. This tutorial introduces you to Java annotations.
What you’ll learn in this Java tutorial
- The four elements of a Java annotation
- Using
@interface
to declare annotation types - Meta-annotation types and the problem of flexibility
- How to process annotations
- Java’s pre-defined annotation types
Elements of a Java annotation
An annotation in Java consists of four elements:
- An
@interface
mechanism for declaring annotation types. - Meta-annotation types, which you can use to identify the application elements an annotation type applies to, identify the lifetime of an annotation (an instance of an annotation type), and more.
- Support for annotation processing via an extension to the Java Reflection API and a generalized tool for processing annotations.
- Java’s standard (pre-defined) annotation types.
You’ll learn how to use each of these elements in your Java annotations.
Using @interface to declare annotation types
You can declare an annotation type by specifying the @
symbol immediately followed by the interface
reserved word and an identifier. For example, Listing 1 declares a simple annotation type that you might use to annotate thread-safe code.
Listing 1. ThreadSafe.java
public @interface ThreadSafe
{
}
After declaring this annotation type, you would prefix the methods that you considered thread-safe with instances of this type by adding the @
symbol followed by the type name to the method headers.
Listing 2 shows a simple example where the main()
method is annotated @ThreadSafe
.
Listing 2. AnnDemo.java (version 1)
public class AnnDemo
{
@ThreadSafe
public static void main(String[] args)
{
}
}
ThreadSafe
instances supply no metadata other than the annotation type name. However, you can supply metadata by adding elements to this type, where an element is a method header placed in the annotation type’s body.
As well as not having code bodies, elements are subject to the following restrictions:
- The method header cannot declare parameters.
- The method header cannot provide a throws clause.
- The method header’s return type must be a primitive type (e.g.,
int
),java.lang.String
,java.lang.Class
, an enum, an annotation type, or an array of one of these types. No other type can be specified for the return type.
As another example, Listing 3 presents a ToDo
annotation type with three elements identifying a particular coding job, specifying the date when the job is to be finished, and naming the coder responsible for completing the job.
Listing 3. ToDo.java (version 1)
public @interface ToDo
{
int id();
String finishDate();
String coder() default "n/a";
}
Note that each element declares no parameter(s) or throws clause, has a legal return type (int
or String
), and terminates with a semicolon. Also, the final element reveals that a default return value can be specified; this value is returned when an annotation doesn’t assign a value to the element.
Listing 4 uses ToDo
to annotate an unfinished class method.
Listing 4. AnnDemo.java (version 2)
public class AnnDemo
{
public static void main(String[] args)
{
String[] cities = { "New York", "Melbourne", "Beijing", "Moscow",
"Paris", "London" };
sort(cities);
}
@ToDo(id = 1000, finishDate = "10/10/2019", coder = "John Doe")
static void sort(Object[] objects)
{
}
}
Listing 4 assigns a metadata item to each element; for example, 1000
is assigned to id
. Unlike coder
, the id
and finishDate
elements must be specified; otherwise, the compiler will report an error. When coder
isn’t assigned a value, it assumes its default "n/a"
value.
Java provides a special String value()
element that can be used to return a comma-separated list of metadata items. Listing 5 demonstrates this element in a refactored version of ToDo
.
Listing 5. ToDo.java (version 2)
public @interface ToDo
{
String value();
}
When value()
is an annotation type’s only element, you don’t have to specify value
and the =
assignment operator when assigning a string to this element. Listing 6 demonstrates both approaches.
Listing 6. AnnDemo.java (version 3)
public class AnnDemo
{
public static void main(String[] args)
{
String[] cities = { "New York", "Melbourne", "Beijing", "Moscow",
"Paris", "London" };
sort(cities);
}
@ToDo(value = "1000,10/10/2019,John Doe")
static void sort(Object[] objects)
{
}
@ToDo("1000,10/10/2019,John Doe")
static boolean search(Object[] objects, Object key)
{
return false;
}
}
Meta-annotation types and the problem of flexibility
You can annotate types (e.g., classes), methods, local variables, and more. However, this flexibility can be problematic. For example, you might want to restrict ToDo
to methods only, but nothing prevents it from being used to annotate other application elements, as demonstrated in Listing 7.
Listing 7. AnnDemo.java (version 4)
@ToDo("1000,10/10/2019,John Doe")
public class AnnDemo
{
public static void main(String[] args)
{
@ToDo(value = "1000,10/10/2019,John Doe")
String[] cities = { "New York", "Melbourne", "Beijing", "Moscow",
"Paris", "London" };
sort(cities);
}
@ToDo(value = "1000,10/10/2019,John Doe")
static void sort(Object[] objects)
{
}
@ToDo("1000,10/10/2019,John Doe")
static boolean search(Object[] objects, Object key)
{
return false;
}
}
In Listing 7, ToDo
is also used to annotate the AnnDemo
class and cities
local variable. The presence of these erroneous annotations might confuse someone reviewing your code, or even your own annotation processing tools. For the times when you need to narrow an annotation type’s flexibility, Java offers the Target
annotation type in its java.lang.annotation
package.
Target
is a meta-annotation type—an annotation type for annotating annotation types. This is different from a non-meta-annotation type, whose annotations annotate application elements such as classes and methods. The Target
annotation type identifies the kinds of application elements to which an annotation type is applicable. These elements are identified by Target
’s ElementValue[] value()
element.
java.lang.annotation.ElementType
is an enum whose constants describe application elements. For example, CONSTRUCTOR
applies to constructors and PARAMETER
applies to parameters. Listing 8 refactors Listing 5’s ToDo
annotation type to restrict it to methods only.
Listing 8. ToDo.java (version 3)
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
public @interface ToDo
{
String value();
}
Given the refactored ToDo
annotation type, an attempt to compile Listing 7 now results in the following error message:
AnnDemo.java:1: error: annotation type not applicable to this kind of declaration
@ToDo("1000,10/10/2019,John Doe")
^
AnnDemo.java:6: error: annotation type not applicable to this kind of declaration
@ToDo(value="1000,10/10/2019,John Doe")
^
2 errors
Additional meta-annotation types
Java 5 introduced three important meta-annotation types, which are found in the java.lang.annotation
package:
Retention
indicates how long annotations with the annotated type are to be retained. This type’s associatedjava.lang.annotation.RetentionPolicy
enum declares the following constants:CLASS
: The compiler records annotations in a class file and the virtual machine doesn’t retain them. This is the default policyRUNTIME
: The compiler records annotations in a class file and the virtual machine retains them.SOURCE
: The compiler discards annotations.
Documented
indicates that instances ofDocumented
-annotated annotations are to be documented byjavadoc
and similar tools.Inherited
indicates that an annotation type is automatically inherited.
Another meta-annotation type, introduced in Java 8, is java.lang.annotation.Repeatable
. Repeatable
is used to indicate that the annotation type whose declaration it (meta-)annotates is repeatable. In other words, you can apply multiple annotations from the same repeatable annotation type to an application element, as demonstrated here:
@ToDo(value = "1000,10/10/2019,John Doe")
@ToDo(value = "1001,10/10/2019,Kate Doe")
static void sort(Object[] objects)
{
}
This example assumes that ToDo
has been annotated with the Repeatable
annotation type.
How to process annotations
Annotations are meant to be processed; otherwise, there’s no point in having them. Java 5 extended the Java Reflection API to help you create your own annotation processing tools. For example, Class
declares an Annotation[]
getAnnotations()
method that returns an array of java.lang.Annotation
instances. These instances describe annotations present on the element described by the Class
object.
Listing 9 presents a simple application that loads a class file, interrogates its methods for ToDo
annotations, and outputs the components of each found annotation.
Listing 9. AnnProcDemo.java
import java.lang.reflect.Method;
public class AnnProcDemo
{
public static void main(String[] args) throws Exception
{
if (args.length != 1)
{
System.err.println("usage: java AnnProcDemo classfile");
return;
}
Method[] methods = Class.forName(args[0]).getMethods();
for (int i = 0; i < methods.length; i++)
{
if (methods[i].isAnnotationPresent(ToDo.class))
{
ToDo todo = methods[i].getAnnotation(ToDo.class);
String[] components = todo.value().split(",");
System.out.printf("ID = %s%n", components[0]);
System.out.printf("Finish date = %s%n", components[1]);
System.out.printf("Coder = %s%n%n", components[2]);
}
}
}
}
After verifying that exactly one command-line argument (identifying a class file) has been specified, main()
loads the class file via Class.forName()
, invokes getMethods()
to return an array of java.lang.reflect.Method
objects identifying all public
methods in the class file, and processes these methods.
Method processing begins by invoking Method
’s boolean isAnnotationPresent(Class<? extends
Annotation> annotationClass)
method to determine if the annotation described by ToDo.class
is present on the method. If so, Method
’s <T extends Annotation> T getAnnotation(Class<T>
annotationClass)
method is called to obtain the annotation.
The ToDo
annotations that are processed are those whose types declare a single String value()
element (see Listing 5). Because this element’s string-based metadata is comma-separated, it needs to be split into an array of component values. Each of the three component values is then accessed and output.
Compile this source code (javac AnnProcDemo.java
). Before you can run the application, you’ll need a suitable class file with @ToDo
annotations on its public
methods. For example, you could modify Listing 6’s AnnDemo
source code to include public
in its sort()
and search()
method headers. You’ll also need Listing 10’s ToDo
annotation type, which requires the RUNTIME
retention policy:
Listing 10. ToDo.java (version 4)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ToDo
{
String value();
}
Compile the modified AnnDemo.java
and Listing 10, and execute the following command to process AnnDemo
’s ToDo
annotations:
java AnnProcDemo AnnDemo
If all goes well, you should observe the following output:
ID = 1000
Finish date = 10/10/2019
Coder = John Doe
ID = 1000
Finish date = 10/10/2019
Coder = John Doe
Java’s pre-defined annotation types
Along with the Target
, Retention
, Documented
, and Inherited
meta-annotation types, Java 5 introduced Deprecated
, Override
, and SuppressWarnings
as pre-defined annotation types. These three annotation types are designed to be used in a compiler context only, so their retention policies are set to SOURCE
.
Deprecated
Deprecated
annotates application elements that are to be deprecated, meaning they’ll be phased out and should not be used. The compiler will warn you when a deprecated element is being accessed.
The following java.util.Date
constructor demonstrates Deprecated
:
@Deprecated
public Date(int year, int month, int date, int hrs, int min)
{
// ... body of this constructor
}
Java 9 introduced a pair of elements into this annotation type:
boolean forRemoval
: Indicates whether the annotated element is subject to removal in a future version. The default value isfalse
.String since
: Returns the version in which the annotated element became deprecated. The version string is in the same format and namespace as the value of the@since
javadoc
tag. The default value is the empty string.
These elements are used to alert developers that parts of standard Java APIs are slated for removal in a future Java version and are being phased out, respectively. For example, Java 9 used the since
element to identify the version in which java.io.FileInputStream
’s and java.io.FileOutputStream
’s void finalize()
methods were first deprecated:
@Deprecated(since="9")
protected void finalize() throws IOException
Java 10 also used the forRemoval
element to indicate that these API methods would be removed in a future Java version. These methods were removed in Java 12:
@Deprecated(since="9",forRemoval=true)
protected void finalize() throws IOException
Override
Override
annotates subclass methods that override their superclass counterparts. The compiler reports an error when the subclass method doesn’t override the superclass method.
The following example demonstrates Override
where the java.lang.Runnable
interface’s public void run()
method is overridden by an anonymous class:
Runnable r = new Runnable()
{
@Override
public void run()
{
// ... body of this method
}
};
SuppressWarnings
SuppressWarnings
annotates application elements (and all elements contained in these application elements) where any of the named compiler warnings (e.g., unchecked
) should be suppressed.
The following example uses SuppressWarnings
to suppress an unchecked warning in the context of the (E[])
cast:
public class Container<E>
{
private E[] elements;
// ...
@SuppressWarnings("unchecked")
public Container(int size)
{
// ...
elements = (E[]) new Object[size];
}
// ...
}
Without the @SuppressWarnings("unchecked")
annotation, the compiler would generate a warning about an unchecked cast; specifically, (E[])
casting from Object[]
to E[]
.
You might wonder why I annotated the constructor instead of the expression containing the cast. I did this because Java doesn’t allow applying annotations to expressions.