Architecture & DesignGetting Started with Java Annotations

Getting Started with Java Annotations

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Annotation has been a part of the core Java API from JDK5 onwards. Annotation means adding comments or instructions into the Java code so that the compiler provides a special treatment to the designated element. This feature, when incorporated appropriately, can leverage productivity by reducing bugs in the code. Annotation instructs the compiler to enforce some specific rules. These rules, if violated, crop compiler time errors. Errors are better managed if they surface during compilation simply because error messages often give an idea to pinpoint the problem. Runtime errors are difficult to debug. They are notoriously misleading, especially the logical errors. Apart from incorporating metadata information, annotation plays an important role as a unit testing and error safeguard tool. The best part is that although it embeds enormous supplemental information to the source code, it does not cause any hindrance to the code semantics or flow of the program. Java core APIs and many third-party libraries extensively use annotation, but here we shall restrict ourselves in exploring the concept rather than getting overwhelmed by their uses. However, we shall take a few examples of its use to also show how to create a custom annotation from scratch.

A Specific Scenario

To get a glimpse of how annotation can help us, let’s get into a scenario. Suppose there is a class called Shape.

public class Shape {
   public void setArea(double area) {
      System.out.println("Area of shape: "+area);
   }
}

And, there’s another class called Rectangle, which extends the Shape class in view of the overriding setArea() method.

public class Rectangle extends Shape {
   public void setArea(int area){
      System.out.println("Area of Rectangle:"+ area);
   }
}

Observe that, by mistake, the programmer has written int area as an argument to the overridden setArea() method. Intriguingly, the method now has been overloaded rather than overridden, something the programmer never intended to do. As a result, the code in main, such as:

Shape s=new Rectangle();
s.setArea(100);

results in the following output:

Area of shape: 100.0

but, the programmer was expecting something like this:

Area of Rectangle:100.0

This truly had been the intended output, had the method been overridden instead of overloaded. However, there is a way out. We can add an @Override annotation before the setArea() method of the Rectangle class.

public class Rectangle extends Shape {
   @Override
   public void setArea(int area){
      System.out.println("Area of Rectangle:"+ area);
   }
}

The @Override annotation is a way to instruct the compiler to treat the method differently and emphasize that the method has actually been overridden. The programmer makes his intention clear and precise to the compiler.

Still, our problem is far from over, The compiler now refuses to compile, and gives an error message:

"The method doesn't override or implement a method
   from supertype."

On closer inspection, we can see that there is a mismatch in method signatures between the super class Shape and the subclass Rectangle on setArea(). So, we must make the following changes to the Rectangle class.

public class Rectangle extends Shape {
   @Override
   public void setArea(double area){
      System.out.println("Area of Rectangle:"+ area);
   }
}

This is a trivial yet typical example of logical mistakes and is beyond the compiler’s understanding. As a result, what may seem unexpected result from the point of view of the programmer can only be realized after program execution, or worse, remain unnoticed as a bug in the code. Such mistakes are hard to pinpoint, especially in a situation that involves hundreds of lines of code. One simple annotation (such as @Override) can save a day’s work of the programmer, if used appropriately.

Annotation Types

There are some specific norms to be followed when creating an annotation from scratch.

    • The declaration always begins with @interface.
@interface MyAnnotation {
   String name();
   int value();
}
    • Methods declared should not take any parameters.
    • The return type of the method declared can only be a primitive type, such as int or double, an object type such as String or Class, enum type, another annotation type or an array of the mentioned types.
    • The member functions are always declared, never defined. They are implicitly defined by Java and act like class properties rather than functions. We, however, can provide some default values as follows:
@interface MyAnnotation {
   String name() default
      "MyAnnotation";
   int value() default 101;
}
    • Inheritence is not allowed in the annotation type, and the extends clause is forbidden. But, every annotation, by default, extends the Annotation interface defined in java.lang.annotation. This super interface overrides hashCode(), toString(), and equals() methods of the Object class and specifies a method called annotationType() that returns a Class object.
    • Every annotation is bounded by a Retention Policy that defines the point of disposal. There are three types of Retention Policy enumerated within java.lang.annotation.RetentionPolicy. They are SOURCE, CLASS, and RUNTIME.
      • SOURCE designates annotation retention only in the source file and discarded during compilation.
      • CLASS designates annotation retention until the class files are generated, but are unavailable at runtime through JVM.
      • RUNTIME designates all through persistence—in source, class, and JVM.

So, putting it all together, our annotation interface can be declared as:

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
   String name() default
      "MyAnnotation";
   int value() default 101;
}
  • Annotation can never be generic and the methods can never specify a throws clause.

Reflection on Annotation

The following code demonstrates how we can test an annotation with the help of Reflection. The code is self-explanatory; it shows how we can get hold of an annotation reference and, in turn, get back the values returned by the methods declared within the annotation.

package org.mano.example;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
   String name() default "MyAnnotation";
   int value() default 101;
}

public class MyClass {
   @MyAnnotation(name = "MyAnnotation", value = 101)
   public static void function1() {
      try {
         MyClass obj = new MyClass();
         Class<?> cls = obj.getClass();
         Method method = cls.getMethod("function1");
         MyAnnotation ma =
            method.getAnnotation(MyAnnotation.class);
         System.out.println(ma.name() + " " + ma.value());
      } catch (NoSuchMethodException ex) {
         System.out.println("No such method" + ex);
      }
   }

   public static void main(String[] args){
      function1();
   }
}

Some Predefined Annotations

There are quite a few predefined annotation types in the core Java APIs. These annotations are used mainly to mark a section of code for special treatment by the compiler. Some annotation types defined in java.lang are: @Deprecated, @Override, @SuppressWarnings, @SafeVarargs, and @FunctionalInterface.

  • The @Deprecated annotation indicates a code element that is marked for removal or will not be in use in future version of the APIs. Similarly, annotations we find in javadoc, but with a lowercase ‘d’ such as @deprecated, which essentially means the same thing and denotes the element, is marked for removal in a future version. Use it with caution.
  • The @Override annotation specifies that the element is meant to override a method from the superclass and ensures that the method is overridden in proper manner.
  • @SuppressWarning tells the compiler to stop producing warning messages during compilation. There are two types of warning: deprecation and unchecked. A deprecation warning message is shown on use of a deprecated method from the API library. One of the possible reasons of surfacing unchecked warnings is due to the use of some legacy code that may have changed with the new version of the APIs.
  • @SafeVarargs asserts that the method or constructor does not perform any unsafe operation on its varargs parameter. This annotation suppresses unchecked warnings relating to varargs usage.
  • @FunctionalInterface is the newest annotation type introduced (JavaSE 8). This annotation indicates an interface type to be a functional interface as per the Java Language Specification.

Apart from these there are annotations that are applied mainly to define other annotation, a kind of meta-annotation. These are defined in java.lang.annotation package, such as @Documented, @Inherited, @Native, @Repeatable, @Retention, @Target. Refer to Java API documentation for more information on these.

Conclusion

Annotations are nothing but Java types, typically used to associate information to the program elements to type use in Java code. The annotations discussed here are mainly regular annotations. There is another type of annotation, called meta-annotations. These annotations are used to annotate other annotation. There many built-in annotations available in Java. Also, if need be, we can create our own, as we have seen in the preceding examples.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories