Learn to Use the New Annotation Feature of Java 5.0

Learn to Use the New Annotation Feature of Java 5.0

he new Java 2 Platform Standard Edition 5.0 (the developer version number is 1.5 and the code name is “Tiger”) provides many new features, among them is the ability to annotate Java program elements and to create custom annotation types. Development and deployment tools can then read annotated data (also known as metadata) and process it in some fashion.

Previous versions of Java provided a limited and ad-hoc mechanism for annotating code through JavaDoc comments and keyword modifiers. Tools such as XDoclet provide a slightly more sophisticated, yet non-standard annotation syntax, which piggybacks on top of JavaDoc. But now, with Java 1.5, annotations are a typed part of the Java language and allow for both runtime and compile-time processing of annotation data.

What Are Annotations?
In short, annotations are metadata or data about data. Annotations are said to annotate a Java element. An annotation indicates that the declared element should be processed in some special way by a compiler, development tool, deployment tool, or during runtime.

Older versions of Java have rough-and-ready annotation functionality. A good example is the @deprecated JavaDoc tag. The @deprecated tag is used for more than sheer documentation purposes. This tag has no effect on the code it describes, but causes the compiler to produce warnings if any other code references the tagged element. JavaDoc does not seem to be the proper place for this type of metadata, but there was no other annotation facility in previous versions of Java. With Java 1.5, finally, annotations are a typed part of the language and the version even comes with some with pre-built annotations, one of which can be used to mark a class as deprecated (I’ll cover this later).

The code below shows how you can declare a method that uses an annotation. It is one of Java 1.5’s built-in annotation types:

class Child extends Parent {    @Overrides    public void doWork() {        //do something          }}

In the code above, note that the annotation starts with an “at” (@) sign. This annotation takes no parameters and is merely used to mark a method for some purpose. It is therefore called a marker annotation. There are also normal annotations and single member annotations (more on these later).

Annotations types are blueprints for annotations, similar to how a class is the blueprint for an object. You can create your own custom annotations by defining annotation types.

The code below shows the declaration of a normal annotation type:

public @interface MyAnnotationType {    int someValue();    String someOtherValue();}

Annotations can be analyzed statically before and during compile time. Annotations will likely be used before compile time mainly to generate supporting classes or configuration files. For example, a code generator (XDoclet, for example) can use annotation data in an EJB implementation class to generate EJB interfaces and deployment descriptors for you, reducing both your effort and the error rate. The average developer will probably not be writing code-generation tools, so these annotation types are likely to be used out-of-the-box rather than authored anew.

Author’s Note:

Annotations will also be used for compile-time checking such as to produce warnings and errors for different failure scenarios. An example of an annotation that is used at compile time is the new @Deprecated annotation, which acts the same as the old @deprecated JavaDoc tag. Of course, a compiler has to know how to interpret annotation data that is meant to produce compile-time warnings, so again, annotations that do things at compile time will likely be used frequently, but rarely written by average developers.

Annotations can be useful at runtime as well. Using annotations you could mark code to behave in a particular way whenever it is called. For example, you could mark some methods with a @prelog annotation. Then at runtime, you could analyze the methods that you’re calling and print a particular log message before you begin executing code for that method. One way to achieve this would be through the use of the updated Java 1.5 reflection API. The reflection API now provides access to runtime-accessible annotation data.

annotation consumer) at runtime is fairly advanced.

Annotating Code
Annotations fall into three categories: normal annotations, single member annotations, and marker annotations (see Table 1). Normal and single member annotations can take member values as arguments when you annotate your code.

Normal Annotations?Annotations that take multiple arguments. The syntax for these annotations provides the ability to pass in data for all the members defined in an annotation type.@MyNormalAnnotation(mem1=”val1″, mem2=”val2″)public void someMethod() { … }
Single Member Annotations?An annotation that only takes a single argument has a more compact syntax. You don’t need to provide the member name.@MySingleMemberAnnotation(“a single value”)public class SomeClass { … }
Marker Annotations?These annotations take no parameters. They are used to mark a Java element to be processed in a particular way.@Deprecated public void doWork() { … }

Any Java declaration can be marked with an annotation. That is, an annotation can be used on a: package, class, interface, field, method, parameter, constructor, enum (newly available in Java 1.5), or local variable. An annotation can even annotate another annotation. Such annotations are called meta-annotations.

This code below shows an annotated class, constructor, field, and method.

@ClassLevelAnnotation(arg1="val1", arg2={"arg2.val1","arg2.val2"})public class AnnotationExample {    @FieldLevelAnnotation()    public String field;        @CtorLevelAnnotation()    public AnnotationsTest() {        // code    }    @MethodLevelAnnotationA("val")    @MethodLevelAnnotationB(arg1="val1",arg2="val2")    public void someMethod(String string) {        // code    }    }

As you can see in the ClassLevelAnnotation, annotations may also take arrays of values. When creating an annotation that requires an array you must surround the array of parameters with brackets ( { … } ) and you must comma-separate each array parameter.

Annotation types may define default values for some members. Every member-value pair that does not have a default value must be supplied when you create an annotation.

The annotation-type declaration below uses meta-annotations, which provide information on how the annotation type can be used:

@Retention(RUNTIME) @Target(METHOD)public @interface MyAnnotationType {    String value;}

This code shows how to use meta-annotations when declaring your own annotation type. I will cover the specifics on declaring annotation types and meta-annotation types later, but for now, just know that you can use meta-annotations to annotate other annotations.

Packages annotations are also allowed, but because packages are not explicitly declared in Java, package annotations must be declared in a source file called in the directory containing the source files for the package. This file should contain only a package declaration, preceded by any annotations that should apply to the package. This java file must not contain an actual class definition (which would be illegal anyways, because package-info is not a legal identifier).

Built-in Annotations
Java 1.5 comes packaged with seven pre-built annotations. I will describe these built-in annotations in this section, borrowing much of the wording directly from the Java 5 documentation.


@Target(value=METHOD)@Retention(value=SOURCE)public @interface Override

This annotation is used to indicate that a method declaration is intended to override a method declaration in a superclass. If a method is annotated with this annotation type but does not override a superclass method, compilers are required to generate an error message.

This annotation is useful in avoiding the situation where you think you are overriding a method, but you misspell the method name in the child class. The code in the method you are trying to override in the parent class will be used because the name is misspelled in the child class. This is usually a difficult bug to track down because no compiler or runtime error is thrown. But marking a method with the Override annotation would help you realize this type of problem at compile time rather than through arduous debugging.


@Documented@Retention(value=RUNTIME)public @interface Deprecated

A program element annotated @Deprecated is one that programmers are discouraged from using, typically because it is dangerous or because a better alternative exists. Using this annotation, compilers warn when a deprecated program element is used or overridden in non-deprecated code.


@Target(value={TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})@Retention(value=SOURCE)public @interface SuppressWarnings

This annotation indicates that the named compiler warnings should be suppressed in the annotated element (and in all program elements contained in the annotated element). 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.


@Documented@Retention(value=RUNTIME)@Target(value=ANNOTATION_TYPE)public @interface Documented

Annotations with a type declaration are to be documented by javadoc and similar tools by default. It should be used to annotate the declarations of types whose annotations affect the use of annotated elements by their clients. If a type declaration is annotated with @Documented, its annotations become part of the public API of the annotated elements.


@Documented@Retention(value=RUNTIME)@Target(value=ANNOTATION_TYPE)public @interface Inherited

This indicates that an annotation type is automatically inherited. If an inherited meta-annotation is present on an annotation type declaration, and the user queries the annotation type on a class declaration, and the class declaration has no annotation for this type, then the class’s superclass will automatically be queried for the annotation type. This process will be repeated until an annotation for this type is found or the top of the class hierarchy (Object) is reached. If no superclass has an annotation for this type, then the query will indicate that the class in question has no such annotation.

Note that this meta-annotation type has no effect if the annotated type is used for anything other than a class. Also, this meta-annotation causes annotations to be inherited only from superclasses; annotations on implemented interfaces have no effect.


@Documented@Retention(value=RUNTIME)@Target(value=ANNOTATION_TYPE)public @interface Retention

Retention takes a single value of the enumerated type RetentionPolicy, which informs the compiler of its policy for retaining annotations in memory. There are three RetentionPolicy enumerated values:

  • SOURCE?Annotations are to be discarded by the compiler.
  • CLASS?Annotations are to be recorded in the class file by the compiler but need not be retained by the VM at runtime. This is the default behavior.
  • RUNTIME?Annotations are to be recorded in the class file by the compiler and retained by the VM at runtime, so they may be read reflectively.

If no Retention annotation is present on an annotation type declaration, the retention policy defaults to RetentionPolicy.CLASS. By default, the annotation data is not available in the JVM in order to reduce overhead.


@Documented@Retention(value=RUNTIME)@Target(value=ANNOTATION_TYPE)public @interface Target

@Target indicates the kinds of program element to which an annotation type is applicable. It takes a single enumerated parameter of type ElementType. The enumerated values are as follows:

  • TYPE (class, interface or enum declaration)
  • FIELD (includes enum constants)

If a @Target meta-annotation is not present on an annotation type declaration, the declared type may be used on any program element. If such a meta-annotation is present, the compiler will enforce the specified usage restriction.

Declaring Annotation Types
Now that you’ve learned a little about the annotations that come packaged with Java 1.5, you can move on to declaring your own annotation types.

Here is a sample annotation type:

public @interface MyAnnotationType {    int someValue();    String someOtherValue();    String yesSomeOtherValue() default "[blank]";}

As you can see, annotation types are declared similarly to interfaces. In fact they use the interface keyword prepended with an @ sign. Just like an interface, annotations have method declarations, each of which defines a member of the annotation. These methods may not have any parameters. As shown in the example, default values are specified after the parenthesis on a method declaration.

At first, it seems a little odd to specify the members of an annotation using method syntax, because when you declare a Java element and annotate it (e.g. when you annotate a class), you may pass values into the annotation. But you are not the consumer of the annotation at this point; you are merely constructing an instance of the annotation. Think of it as calling a constructor on an implicit Java class that you didn’t have to write.

The annotation consumers are the development tools, the compiler, or a runtime library that accesses the annotation data you created when you annotated your Java code. After you’ve created the annotation, annotation consumers may call the methods on the annotation interface in order to get the annotation values. The return types of the methods represent the type of data that a consumer of the annotation would get back.

There are a few restrictions when defining annotation types.

  • Annotations cannot extend other annotations, except for the java.lang.annotation.Annotation marker interface they inherently extend.
  • Method return types must be: primitive types, String, Class, enum types, annotation types, or arrays of the preceding types.
  • Annotations can’t have a throws clause.
  • Self reference is not allowed (e.g. AnnotationA contains a member of type AnnotationA), nor circular reference (e.g. AnnotationA contains a member of type AnnotationB and vice versa).
  • Single member annotations must define only a single method called value in the annotation type. So long as you follow this construct, you can use the condensed single-member syntax when creating an annotation of this type.

    A single member annotation can be defined as follows:

    public @interface Copyright {    String value();}

You can also create meta-annotations, as we have already seen. A meta-annotation is any annotation that can be used to annotate other annotations. For example, all of the build-in annotations utilize meta-annotations and some of them are meta-annotations themselves.

Here is the declaration for the @ Inherited meta-annotation type:

@Documented@Retention(value=RUNTIME)@Target(value=ANNOTATION_TYPE)public @interface Inherited

The @Inherited annotation uses three other meta-annotations and is a meta-annotation itself. What makes it a meta-annotation? Any annotation whose @Target annotation member includes ANNOTATION_TYPE can be used as a meta-annotation.

Reading Annotations
Now that you know how to declare your own annotation types and annotate Java elements, you need to know how to access the annotation data you specified. Annotation consumers are software that read annotation data and do something useful with it. Annotation consumers fall into three groups:

General tools are programs that can analyze source code and do something useful with it. For example compilers and documentation generators are both considered general tools. General tools do not load annotated classes or annotation interfaces into the virtual machine.

Specific tools are also programs that can analyze source code without loading annotated classes, but they need to load annotation interfaces into the virtual machine. An example of this is a Stub generator.

Introspectors are programs that can query their own annotations (the ones with a RUNTIME retention policy). Introspectors will load both annotated classes and annotation interfaces into the virtual machine.

Listing 1 is an example of how you can access your code during runtime using the reflection API.

As I mentioned previously, I think that most developers will be users of annotations, but few will have to write code that consumes annotations.

If you are a general or specific tools developer, you may want to look into a few APIs that will help you read annotations from source files in order to use them for some type of static preprocessing. The new Doclet API (com.sun.javadoc) has support for reading annotations. It doesn’t currently support all of the features of annotations, but it may suffice as a library for writing your tool. Another thing to keep an eye on is the newly submitted JSR-269 (Pluggable Annotation Processing API), which will provide a generic API that allows developers to read annotation data using general and specific tools.

If you are developing an introspector, you can use the new reflection API to access your annotations, though I would suggest looking into AOP as a means to interface with annotated methods. I think there is a lot of room for exploration here. The combinations of aspects and annotations can yield a very powerful introspection tool.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist