Annotations are a new language feature introduced in Java 5 that allows Java code elements such as classes or methods to be annotated with structured metadata. This metadata can then be used at compile-time or at run-time by other code. Annotations are commonly used to provide configuration information for infrastructure frameworks to provide cross-cutting functionality.

Using existing annotations is fairly easy - an annotation is prefixed with ‘@’ and optionally suffixed with one or more arguments of the form (<single-arg-value>) or (<arg1=value1>, <arg2=value2>, …). The annotation is added immediately before the Java code element being annotated. Below is an example using two of the standard annotations defined in Java 5 (@Override and @SuppressWarnings):

package com.basilv.examples.annotations;

import java.util.ArrayList;

@SuppressWarnings("unchecked")
public class AnnotationUsage
{
  public void doStuff() {
    ArrayList list = new ArrayList();
    list.add("");
  }

  @Override
  public String toString() {
    return "AnnotationUsage";
  }
}

Defining a new annotation is a little more complicated. While an annotation is conceptually fairly similar to a Java interface - it has a name and a set of properties - the syntax for defining an annotation is, unfortunately, somewhat different from the syntax used to define an interface. This is best explained with an example:

package com.basilv.examples.annotations;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target( {ElementType.TYPE, ElementType.METHOD })
public @interface MyAnnotation {
  int myProperty();
  String myPropertyWithDefault() default "";
}

The @interface syntax is bizarre - I mentally convert it to annotation - i.e. public annotation MyAnnotation. (I am not sure why Sun did not go with this more intuitive syntax, but I assume they did not want to create another reserved keyword for compatibility reasons - a weak reason considering that they added the assert keyword in v1.4 and the enum keyword in v5.) The declaration of individual properties is similar to the declaration of methods in interfaces except for the optional default clause that can be supplied. If no default is supplied then a value for the property must be supplied when using the annotation.

Annotations with no properties are allowed and are called marker annotations. Annotations with a single property by convention name the property value(), which allows the annotation property value to be declared without the name of the property: i.e. @Annotation("value").

The other interesting quirk is that Java annotations (@Retention and @Target) are used when declaring an annotation. The @Retention annotation defines how the metadata for uses of this annotation is retained via the RetentionPolicy enum. The default is RetentionPolicy.CLASS, which stores the metadata in the compiled class file but does not necessarily load it at runtime. I think the default should have been RetentionPolicy.RUNTIME, which ensures that the Java VM will load the metadata and make it available at runtime. When coding my first annotation I forgot to specify the runtime retention policy. This leads to the puzzling error of not being able to retrieve at runtime the metadata on the classes using the annotation, with no indication of what was wrong. The @Target annotation specifies the Java code elements that this annotation can be applied to.

Annotation metadata is only useful if we can retrieve and act on it. The Reflections API in Java 5 was augmented with methods to retrieve annotation metadata for java code elements such as classes and methods. These annotation related methods are defined by a common interface java.lang.reflect.AnnotatedElement. Examples include boolean isAnnotationPresent(Class) and T getAnnotation(Class).

The following example shows a somewhat-realistic example of an annotation being used. I define an annotation to map URLs to methods on a class, and then implement a servlet to delegate requests to the appropriate method using this metadata. First the definition of the annotation:

package com.basilv.examples.annotations;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)// or source or compile
@Target( { ElementType.TYPE, ElementType.METHOD })
public @interface WebAction {
  String url();
}

Now the class which uses this annotation to map URLs to particular methods:

package com.basilv.examples.annotations;

import javax.servlet.http.*;

public class ActionExecutor
{
  private HttpServletRequest request;
  private HttpServletResponse response;

  public ActionExecutor(HttpServletRequest request,
    HttpServletResponse response) {
    this.request = request;
    this.response = response;
  }

  private void setMessage(String message) {
    request.setAttribute("message", message);
  }

  @WebAction(url = "/test")
  public void doTest() {
    // Set a message for demo purposes. Normally execute some logic.
    setMessage("test");
  }

  @WebAction(url = "/submit")
  public void submitWork() {
    // Set a message for demo purposes. Normally execute some logic.
    setMessage("submit");
  }
}

Now the servlet to handle requests by executing the appropriate method on ActionExecutor:

package com.basilv.examples.annotations;

import java.io.IOException;
import java.lang.reflect.Method;
import javax.servlet.*;
import javax.servlet.http.*;

public class DispatchingServlet extends HttpServlet
{
  @Override
  protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException,
    IOException {
    response.setContentType("text/html;charset=UTF-8");

    String url = request.getPathInfo();
    ActionExecutor executor = new ActionExecutor(request,
      response);

    boolean actionFound = false;
    for (Method method : ActionExecutor.class.getMethods()) {
      if (method.isAnnotationPresent(WebAction.class)) {
        WebAction action = method.getAnnotation(
          WebAction.class);
        if (url.equals(action.url())) {
          actionFound = true;
          try {
            method.invoke(executor, (Object[]) null);
          } catch (Exception e) {
            throw new RuntimeException(
              "Error invoking method [" + method.getName()
                + "].", e);
          }
        }
      }
    }
    if (!actionFound) {
      request.setAttribute("message", "No action found");
    }
    RequestDispatcher rd = request
      .getRequestDispatcher("/view.jsp");
    rd.forward(request, response);
  }

  @Override
  protected void doPost(HttpServletRequest request,
    HttpServletResponse response) throws ServletException,
    IOException {
    doGet(request, response);
  }
}

There are a number of scenarios for which it would be useful to iterate over all classes with a particular annotation. The above example of the dispatching servlet would be more useful if it could dispatch to methods on any number of classes based on a class-level annotation. The Reflections API, however, does not provide a mechanism for obtaining all classes with a particular annotation. This is difficult functionality to provide for a number of reasons explained by Bill Burke, but since it is so valuable various open source projects such as Spring and JBoss have implemented solutions to this.

If you find this article helpful, please make a donation.

Share this article: Add 'Working with Java 5 Annotations' to reddit Add 'Working with Java 5 Annotations' to digg Add 'Working with Java 5 Annotations' to Del.icio.us Add 'Working with Java 5 Annotations' to FURL Add 'Working with Java 5 Annotations' to Technorati Add 'Working with Java 5 Annotations' to Yahoo My Web Add 'Working with Java 5 Annotations' to Newsvine