Annotations

Annotations provide a way to associate arbitrary information or metadata with program elements. Syntactically, annotations are used like modifiers and can be applied to the declarations of packages, types, constructors, methods, fields, parameters, and local variables. The information stored in an annotation takes the form of name = value pairs, whose type is specified by the annotation type. The annotation type is a kind of interface that also serves to provide access to the annotation through the Java Reflection API.

Annotations can be used to associate any kind of information you want with a program element. The only fundamental rule is that an annotation cannot affect the way the program runs: the code must run identically even if you add or remove annotations. Another way to say this is that the Java interpreter ignores annotations (although it does make “runtime-visible” annotations available for reflective access through the Java Reflection API). Since the Java VM ignores annotations, an annotation type is not useful unless accompanied by a tool that can do something with the information stored in annotations of that type. In this chapter we’ll cover standard annotation and meta-annotation types like Override and Target. The tool that accompanies these types is the Java compiler, which must process them in certain ways (as we’ll describe later in this section).

It is easy to imagine any number of other uses for annotations.[7] A local variable might be annotated with a type named NonNull, as an assertion that the variable would never have a null value. An associated (hypothetical) code-analysis tool could then parse the code and attempt to verify the assertion. The JDK includes a tool named apt (for Annotation Processing Tool) that provides a framework for annotation processing tools: it scans source code for annotations and invokes specially written annotation processor classes that you provide. See Chapter 8 for more on apt. Annotations will probably find their widest use in enterprise programming where they may replace tools such as XDoclet, which processes metadata embedded in ad-hoc javadoc comments.

This section begins with an introduction to annotation-related terminology. We then cover the standard annotation types introduced in Java 5.0, annotations supported by javac that you can use in your programs right away. Next, we describe the syntax for writing arbitrary annotations and briefly cover the use of the Java Reflection API for querying annotations at runtime. At this point, we move on to more esoteric material on defining new annotation types, a task that few programmers will ever need to do. This final part of the chapter also discusses meta-annotations.

Annotation Concepts and Terminology

The key concept to understand about annotations is that an annotation simply associates information or metadata with a program element. Annotations never affect the way a Java program runs, but they may affect things like compiler warnings or the behavior of auxiliary tools such as documentation generators, stub generators, and so forth.

The following terms are used frequently when discussing annotations. Of particular importance is the distinction between annotation and annotation type.

annotation

An annotation associates arbitrary information or metadata with a Java program element. Annotations use new syntax introduced in Java 5.0 and behave like modifiers such as public or final. Each annotation has a name and zero or more members. Each member has a name and a value, and it is these name = value pairs that carry the annotation’s information.

annotation type

The name of an annotation as well as the names, types, and default values of its members are defined by the annotation type . An annotation type is essentially a Java interface with some restrictions on its members and some new syntax used in its declaration. When you query an annotation using the Java Reflection API, the returned value is an object that implements the annotation type interface and allows individual annotation members to be queried. Java 5.0 includes three standard annotation types in the java.lang package. We’ll see these annotations in Section 4.3.2 later in this chapter.

annotation member

The members of an annotation are declared in an annotation type as no-argument methods. The method name and return type define the name and type of the member. A special default syntax allows the declaration of a default value for any annotation member. An annotation appearing on a program element includes name = value pairs that define values for all annotation members that do not have default values and may also include values that override the defaults of other members.

marker annotation

An annotation type that defines no members is called a marker annotation . An annotation of this type carries information simply by its presence or absence.

meta-annotation

A meta-annotation is an annotation applied to the declaration of an annotation type. Java 5.0 includes several standard meta-annotation types in the java.lang.annotation package. They are used to specify things like which program elements the annotation can be applied to.

target

The target of an annotation is the program element that is annotated. Annotations can be applied to packages, types (classes, interfaces, enumerated types, and even annotation types), type members (methods, constructors, fields, and enumerated values), method parameters, and local variables (including loop variables and catch parameters). The declaration of an annotation type may include a meta-annotation that restricts the allowable targets for that type of annotation.

retention

The retention of an annotation specifies how long the information contained in the annotation is retained. Some annotations are discarded by the compiler and appear only in source code. Others are compiled into the class file. Of those that are compiled into the class file, some are ignored by the virtual machine, and others are read by the virtual machine when the class that contains them is loaded. The declaration of an annotation type can use a meta-annotation to specify the retention for annotations of that type. Annotations that are loaded by the VM are runtime-visible and can be queried by the reflective APIs of java.lang.reflect.

metadata

When discussing annotations, the term metadata commonly refers to the information carried by an annotation or to the annotation itself. Because this term is used in many different ways in computer programming literature, I have avoided using it in this chapter.

Using Standard Annotations

Java 5.0 defines three standard annotation types in the java.lang package. The following sections describe these annotation types and explain how to use them to annotate your code.

Override

java.lang.Override is a marker annotation type that can be used to annotate methods but no other program element. An annotation of this type serves as an assertion that the annotated method overrides a method of a superclass. If you use this annotation on a method that does not override a superclass method, the compiler issues a compilation error to alert you to this fact.

This annotation is intended to address a common category of programming errors that result when you attempt to override a superclass method but get the method name or signature wrong. In this case, you may have overloaded the method name but not actually overridden the method, and your code never gets invoked.

To use this annotation type, simply include @Override in the modifiers of the desired method. By convention, @Override comes before other modifiers. Also by convention, there is no space between the @ character and the name Override, even though it is technically allowed. Note that because the java.lang package is always automatically imported, you never need to include the package name to use this annotation type. Here is an example in which the @Override annotation is used on a method that fails to correctly override the toString() method of its superclass.

@Override
public String toSting() {   // Oops.  Note the misspelling here!
    // Simply put square brackets around our superclass's output
    return "[" + super.toString() + "]";
}

Without the annotation, the typo might go unnoticed and we’d have a puzzling bug: why isn’t the toString( ) method working correctly? But with the annotation, the compiler gives us the answer: the toString() method does not work as expected because it is not actually overridden.

Note that the @Override annotation applies only to methods that are intended to override a superclass method and not to methods that are intended to implement a method defined in an interface. The compiler already produces an error if you fail to correctly implement an interface method.

Deprecated

java.lang.Deprecated is a marker annotation that is similar to the @deprecated javadoc tag. (See Chapter 7 for details on writing Java documentation comments.) If you annotate a type or type member with @Deprecated, it tells the compiler that use of the annotated element is discouraged. If you use (or extend or override) a deprecated type or member from code that is not itself declared @Deprecated, the compiler issues a warning.

Note that the @Deprecated annotation type does not deprecate the @deprecated javadoc tag. The @Deprecated annotation is intended for the Java compiler. The javadoc tag, on the other hand, is intended for the javadoc tool and serves as documentation: it may include a description of why the program element has been deprecated and what it has been superseded by or replaced with.

In Java 5.0, the compiler continues to look for @deprecated javadoc tags and uses them to generate warnings as it always has. This behavior may be phased out, however, and you should begin to use the @Deprecated annotation in addition to the @deprecated javadoc tag.

Here is an example that uses both the annotation and the javadoc tag:

/**
 * The Sony Betamax video cassette format.
 * @deprecated No one has players for this format any more.  Use VHS instead.
 */
@Deprecated public class Betamax { ... }

SuppressWarnings

The @SuppressWarnings annotation is used to selectively turn off compiler warnings for classes, methods, or field and variable initializers.[8] In Java 5.0, Sun’s javac compiler has a powerful -Xlint option that causes it to issue warnings about “lint” in your program—code that is legal but is likely to represent a programming error. These warnings include the “unchecked warning” that appears when you use a generic collection class without specifying a value for its type parameters, for example, or the warning that appears if a case in a switch statement does not end with a break, return, or throw and allows control to “fall through” to the next case.

Typically, when you see one of these lint warnings from the compiler, you should investigate the code that caused it. If it truly represents an error, you then correct it. If it simply represents sloppy programming, you may be able to rewrite your code so that the warning is no longer necessary. For example, if the warning tells you that you have not covered all possible cases in a switch statement on an enumerated type, you can avoid the warning by adding a defensive default case to the switch statement, even if you are sure that it will never be invoked.

On the other hand, sometimes there is nothing you can do to avoid the error. For example, if you use a generic collection class in code that must interact with nongeneric legacy code, you cannot avoid an unchecked warning. This is where @SuppressWarnings comes in: add this annotation to the nearest relevant set of modifiers (typically on method modifiers) to tell the compiler that you’re aware of the issue and that it should stop pestering you about it.

Unlike Override and Deprecated, SuppressWarnings is not a marker annotation. It has a single member named value whose type is String[ ]. The value of this member is the names of the warnings to be suppressed. The SuppressWarnings annotation does not define what warning names are allowed: this is an issue for compiler implementors. For the javac compiler, the warning names accepted by the -Xlint option are also legal for the @SuppressWarnings annotation. It is legal to specify any warning names you want: compilers ignore (but may warn about) warning names they do not recognize.

So, to suppress warnings named unchecked and fallthrough, you could use an annotation that looks like the following. Annotation syntax follows the name of the annotation type with a parenthesized, comma-separated list of name = value pairs. In this case, the SuppressWarnings annotation type defines only a single member, so there is only a single pair within parentheses. Since the member value is an array, curly braces are used to delimit array elements:

@SuppressWarnings(value={"unchecked","fallthrough"})
public void lintTrap() { /* sloppy method body omitted */ }

We can abbreviate this annotation somewhat. When an annotation has a single member and that member is named “value”, you are allowed (and encouraged) to omit the “value=” in the annotation. So the annotation above should be rewritten as:

@SuppressWarnings({"unchecked","fallthrough"})

Hopefully you will not often have more than one unresolvable lint warning in any particular method and will need to suppress only a single named warning. In this case, another annotation abbreviation is possible. When writing an array value that contains only a single member, you are allowed to omit the curly braces. In this case we might have an annotation like this:

@SuppressWarnings("unchecked")

Annotation Syntax

In the descriptions of the standard annotation types, we’ve seen the syntax for writing marker annotations and the syntax for writing single-member annotations, including the shortcut allowed when the single member is named “value” and the shortcut allowed when an array-typed member has only a single array element. This section describes the complete syntax for writing annotations.

An annotation consists of the @ character followed by the name of the annotation type (which may include a package name) followed by a parenthesized, comma-separated list of name = value pairs for each of the members defined by the annotation type. Members may appear in any order and may be omitted if the annotation type defines a default value for that member. Each value must be a literal or compile-time constant, a nested annotation, or an array.

Near the end of this chapter, we define an annotation type named Reviews that has a single member that is an array of @Review annotations. The Review annotation type has three members: “reviewer” is a String, “comment” is an optional String with a default value, and “grade” is a value of the nested enumerated type Review.Grade. Assuming that the Reviews and Review types are properly imported, an annotation using these types might look like this (note the use of nested annotations, enumerated types, and arrays in this annotation):

@Reviews({  // Single-value annotation, so "value=" is omitted here
    @Review(grade=Review.Grade.EXCELLENT,
            reviewer="df"),
    @Review(grade=Review.Grade.UNSATISFACTORY,
            reviewer="eg",
            comment="This method needs an @Override annotation")
})

Another important rule of annotation syntax is that no program element may have more than one instance of the same annotation. It is not legal, for example, to simply place multiple @Review annotations on a class. This is why the @Reviews annotation is defined to allow an array of @Review annotations.

Annotation member types and values

The values of annotation members must be non-null compile-time constant expressions that are assignment-compatible with the declared type of the member. Allowed member types are the primitive types, String, Class, enumerated types, annotation types, and arrays of any of the above types (but not an array of arrays). For example, the expressions 2*Math.PI and "hello"+"world" are legal values for members of type double and String, respectively.

Near the end of the chapter, we define an annotation type named UncheckedExceptions whose sole member is an array of classes that extend RuntimeException. An annotation of this type might look like this:

@UncheckedExceptions({
    IllegalArgumentException.class, StringIndexOutOfBoundsException.class
})

Annotation targets

Annotations are most commonly placed on type definitions (such as classes) and their members (such as methods and fields). Annotations may also appear on packages, parameters, and local variables. This section provides more information about these less common annotation targets.

A package annotation appears before the package declaration in a file named package-info.java. This file should not contain any type declarations (“package-info” is not a legal Java identifier, so it cannot contain any public type definitions). Instead, it should contain an optional javadoc comment, zero or more annotations, and a package declaration. For example:

/**
 * This package holds my custom annotation types.
 */
@com.davidflanagan.annotations.Author("David Flanagan")
package com.davidflanagan.annotations;

When the package-info.java file is compiled, it produces a class file named package-info.class that contains a synthetic interface declaration. This interface has no members, and its name, package-info, is not a legal Java identifier, so it cannot be used in Java source code. It exists simply as a placeholder for package annotations with class or runtime retention.

Note that package annotations appear outside the scope of any package or import declaration. This means that package annotations should always include the package name of the annotation type (unless the package is java.lang).

Annotations on method parameters, catch clause parameters, and local variables simply appear as part of the modifier list for those program elements. The Java class file format has no provision for storing annotations on local variables or catch clause parameters, so those annotations always have source retention. Method parameter annotations can be retained in the class file, however, and may have class or runtime retention.

Finally, note that the syntax for enumerated type definitions does not allow any modifiers to be specified for enumerated values. It does, however, allow annotations on any of the values.

Annotations and defaults

Annotations must include a value for every member that does not have a default value defined by the annotation type. Annotations may, of course, include values for other members as well.

There is one important detail to understand about how default values are handled. Default values are stored in the class file of the annotation type and are not compiled into annotations themselves. If you modify an annotation type so that the default value of one of its members changes, that change affects all annotations of that type that do not specify an explicit value for that member. Already-compiled annotations are affected, even if they are never recompiled after the change to the type.

Annotations and Reflection

The Reflection API of java.lang.reflect has been extended in Java 5.0 to support reading of runtime-visible annotations. (Remember that an annotation is only visible at runtime if its annotation type is specified to have runtime retention, that is, if the annotation is both stored in the class file and read by the Java VM when the class file is loaded.) This section briefly covers the new reflective capabilities. For full details, look up the interface java.lang.reflect.AnnotatedElement in the reference section. AnnotatedElement represents a program element that can be queried for annotations. It is implemented by java.lang.Package, java.lang.Class, and indirectly implemented by the Method, Constructor, and Field classes of java.lang.reflect. Annotations on method parameters can be queried with the getParameterAnnotations( ) method of the Method or Constructor class.

The following code uses the isAnnotationPresent( ) method of AnnotatedElement to determine whether a method is unstable by checking for an @Unstable annotation. It assumes that the Unstable annotation type, which we’ll define later in the chapter, has runtime retention. Note that this code uses class literals to specify both the class to be checked and the annotation to check for:

import java.lang.reflect.*;

Class c = WhizzBangClass.class;                           
Method m = c.getMethod("whizzy", int.class, int.class);  
boolean unstable = m.isAnnotationPresent(Unstable.class);

isAnnotationPresent() is useful for marker annotations. When working with annotations that have members, though, we typically want to know the value of those members. For this, we use the getAnnotation() method. And here we see the beauty of the Java annotation system: if the specified annotation exists, the object returned by this method implements the annotation type interface, and you can query the value of any member simply by invoking the annotation type method that defines that member. Consider the @Reviews annotation that appeared earlier in the chapter, for example. If the annotation type was declared with runtime retention, you could query it as follows:

AnnotatedElement target = WhizzBangClass.class; // the type to query
// Ask for the @Reviews annotation as an object that implements Reviews
Reviews annotation = target.getAnnotation(Reviews.class);
// Reviews has a single member named "value" that is an array of reviews
Review[] reviews = annotation.value();
// Loop through the reviews
for(Review r : reviews) {
    Review.Grade grade = r.grade();
    String reviewer = r.reviewer();
    String comment = r.comment();
    System.out.printf("%s assigned a grade of %s and comment '%s'%n",
                      reviewer, grade, comment);
}

Note that these reflective methods correctly resolve default annotation values for you. If an annotation does not include a value for a member with a default value, the default value is looked up within the annotation type itself.

Defining Annotation Types

An annotation type is an interface, but it is not a normal one. An annotation type differs from a normal interface in the following ways:

  • An annotation type is defined with the keyword @interface rather than with interface. An @interface declaration implicitly extends the interface java.lang.annotation.Annotation and may not have an explicit extends clause of its own.

  • The methods of an annotation type must be declared with no arguments and may not throw exceptions. These methods define annotation members: the method name becomes the member name, and the method return type becomes the member type.

  • The return value of annotation methods may be a primitive type, a String, a Class, an enumerated type, another annotation type, or a single-dimensional array of one of those types.

  • Any method of an annotation type may be followed by the keyword default and a value compatible with the return type of the method. This strange new syntax specifies the default value of the annotation member that corresponds to the method. The syntax for default values is the same as the syntax used to specify member values when writing an annotation. null is never a legal default value.

  • Annotation types and their methods may not have type parameters—annotation types and members cannot be made generic. The only valid use of generics in annotation types is for methods whose return type is Class. These methods may use a bounded wildcard to specify a constraint on the returned class.

In other ways, annotation types declared with @interface are just like regular interfaces. They may include constant definitions and static member types such as enumerated type definitions. Annotation types may also be implemented or extended just as normal interfaces are. (The classes and interfaces that result from doing this are not themselves annotation types, however: annotation types can be created only with an @interface declaration.)

We now define the annotation types used in our examples. These examples illustrate the syntax of annotation type declarations and demonstrate many of the differences between @interface and interface. We start with the simple marker annotation type Unstable. Because we used this type earlier in the chapter in a reflection example, its definition includes a meta-annotation that gives it runtime retention and makes it accessible to the reflection API. Meta-annotations are covered below.

package com.davidflanagan.annotations;
import java.lang.annotation.*;

/**
 * Specifies that the annotated element is unstable and its API is
 * subject to change.
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface Unstable {}

The next annotation type defines a single member. By naming the member value, we enable a syntactic shortcut for anyone using the annotation:

/**
 * Specifies the author of a program element.
 */
public @interface Author {
    /** Return the name of the author */
    String value();
}

The next example is more complex. The Reviews annotation type has a single member, but the type of the member is complex: it is an array of Review annotations. The Review annotation type has three members, one of which has an enumerated type defined as a member of the Review type itself, and another of which has a default value. Because the Reviews annotation type is used in a reflection example, we’ve given it runtime retention with a meta-annotation:

import java.lang.annotation.*;
        
/**
 * An annotation of this type specifies the results of one or more
 * code reviews for the annotated element
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface Reviews {
    Review[] value();
}

/**
 * An annotation of this type represents a single code review of the
 * annotated element.  Every review must specify the name of the reviewer
 * and the grade assigned to the code.  Optionally, reviews may also include
 * a comment string.
 */
public @interface Review {
    // Nested enumerated type
    public static enum Grade { EXCELLENT, SATISFACTORY, UNSATISFACTORY };

    // These methods define the annotation members
    Grade grade();                // member named "grade" with type Grade
    String reviewer();          
    String comment() default "";  // Note default value here.
}

Finally, suppose we wanted to annotate methods to list the unchecked exceptions (but not errors) that they might throw. Our annotation type would have a single member of array type. Each element of the array would be the Class of an exception. In order to enforce the requirement that only unchecked exceptions are used, we use a bounded wildcard on Class:

public @interface UncheckedExceptions {
    Class<? extends RuntimeException>[] value();
}

Meta-Annotations

Annotation types can themselves be annotated. Java 5.0 defines four standard meta-annotation types that provide information about the use and meaning of other annotation types. These types and their supporting classes are in the java.lang.annotation package, and you can find complete details in the quick-reference section of the book.

Target

The Target meta-annotation type specifies the “targets” for an annotation type. That is, it specifies which program elements may have annotations of that type. If an annotation type does not have a Target meta-annotation, it can be used with any of the program elements described earlier. Some annotation types, however, make sense only when applied to certain program elements. Override is one example: it is only meaningful when applied to a method. An @Target meta-annotation applied to the declaration of the Override type makes this explicit and allows the compiler to reject an @Override when it appears in an inappropriate context.

The Target meta-annotation type has a single member named value. The type of this member is java.lang.annotation.ElementType[]. ElementType is an enumerated type whose enumerated values represent program elements that can be annotated.

Retention

We discussed annotation retention earlier in the chapter. It specifies whether an annotation is discarded by the compiler or retained in the class file, and, if it is retained in the class file, whether it is read by the VM when the class file is loaded. By default, annotations are stored in the class file but not available for runtime reflective access. The three possible retention values (source, class, and runtime) are described by the enumerated type java.lang.annotation.RetentionPolicy .

The Retention meta-annotation type has a single member named value whose type is RetentionPolicy.

Documented

Documented is a meta-annotation type used to specify that annotations of some other type should be considered part of the public API of the annotated program element and should therefore be documented by tools like javadoc. Documented is a marker annotation: it has no members.

Inherited

The @Inherited meta-annotation is a marker annotation that specifies that the annotated type is an inherited one. That is, if an annotation type @Inherited is used to annotate a class, the annotation applies to subclasses of that class as well.

Note that @Inherited annotation types are inherited only by subclasses of an annotated class. Classes do not inherit annotations from interfaces they implement, and methods do not inherit annotations from methods they override.

The Reflection API enforces the inheritance if the @Inherited annotation type is also annotated @Retention(RetentionPolicy.RUNTIME). If you use java.lang.reflect to query a class for an annotation of an @Inherited type, the reflection code checks the specified class and each of its ancestors until an annotation of the specified type is found or the top of the class hierarchy is reached.



[7] We won’t have to imagine these uses for long. At the time of this writing, JSR 250 is making its way through the Java Community Process to define a standard set of common annotations for J2SE and J2EE.

[8] The javac compiler did not yet support the @SuppressWarnings annotation when this chapter was written. Full support is expected in Java 5.1.

Get Java in a Nutshell, 5th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.