My experiments with technology

Java Custom Annotations

January 13, 2008 · 6 Comments

My earlier post covered in-built annotations supported by Java 5. Today we will look at developing custom java annotations on our own.


Let’s begin by defining our own custom java annotation called Documentation. The definition of the annotation class is as below:

package com.vinraj.custom;

public @interface Documentation {

}

The only difference between an interface definition and that of an annotation is the presence of @ before the interface keyword. Now the annotation can have its own members.

package com.vinraj.custom;

public @interface Documentation {

    public String author();

    public String version();

    public String shortDescription();

    public String[] reviews();

}

Below are the general guidelines to be followed while defining annotations:

  1. Annotation declaration should start with an ‘at’ sign like @, following with an interface keyword, following with the annotation name.
  2. Method declarations should not have any parameters.
  3. Method declarations should not have any throws clauses.
  4. Return types of the method should be one of the following:
    • primitives
    • String
    • Class
    • enum
    • array of the above types

Let’s look at how the custom annotation Documentation can be used.

package com.vinraj.custom;

public class NewClass {

    @Documentation(
	author="James Smith",
	version="1.0",
	shortDescription="Testing",
	reviews={"good", "nice"})
    public void test() {

    }
}

The annotation member elements can be set to have default values. Here’s an example:

package com.vinraj.custom;

public @interface Documentation {

    public String author() default "Tim Burton";

    public String version() default "1.0";

    public String shortDescription();

    public String[] reviews();

}

Due to introduction of the default values the NewClass can be defined in the following manner:

package com.vinraj.custom;

public class NewClass {

    @Documentation(
	author="James Smith",
	shortDescription="Testing",
	reviews={"good", "nice"})
    public void test() {

    }

}

We have omitted the version member variable from the annotation Documentation in NewClass.

There are specific annotations which can only be used in the context of annotations. The annotations are target, retention, documented and inherited.

Let’s look at their usage one at a time. First let’s look at target annotation. The target annotation defines which program elements can have annotations of the defined type. The user can put in the following options:

  1. TYPE: Class, interface, or enum (not annotation)
  2. FIELD: member fields (including enum values)
  3. METHOD: methods (does not include constrcutors)
  4. PARAMETER: method parameter
  5. CONSTRUCTOR: constructor
  6. LOCAL_VARIABLE: local variable or catch clause
  7. ANNOTATION_TYPE: Annotation types
  8. PACKAGE: java package

Let’s change the Documentation annotation class to the following.

package com.vinraj.custom;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
public @interface Documentation {

    public String author() default "Tim Burton";

    public String version() default "1.0";

    public String shortDescription();

    public String[] reviews();

}

We have now limited the usage of the Documentation annotation to the class methods. Let’s change the implementation of the NewClass to the following:

package com.vinraj.custom;

public class NewClass {

    @Documentation(
	author="James Smith",
	shortDescription="Testing",
	reviews={"good", "nice"})
    public void test() {

    }
    @Documentation(
	author="James Smith",
	shortDescription="Testing",
	reviews={"good", "nice"})
    public String name = "";

}

A compilation error is raised for using Documentation annotation for class instance variable.

The retention annotation indicates where and how long annotations of this type will be retained. This annotation has three options source, class and runtime. If the retention option selected is source, the annotation is discarded post compilation. Examples of such annotation types are @Override and @SuppressWarnings. The next option is class, the annotation is discarded during class load. The last option is the runtime. The annotation information marked using this option is never discarded. The annotation should be available for reflection at runtime. Example of annotation that could be useful at runtime is @Deprecated.

The Documentation custom annotation can be added the Retention annotation in the following manner.

package com.vinraj.custom;

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 Documentation {

    public String author() default "Tim Burton";

    public String version() default "1.0";

    public String shortDescription();

    public String[] reviews();

}

By default, the annotation and related information does not appear in the class javadoc. A marker annotation @Documented in provided to cause the annotation related information to be added in the class javadoc. The Documentation custom annotation can use the @DOcumented in the following manner.

package com.vinraj.custom;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Documentation {

    public String author() default "Tim Burton";

    public String version() default "1.0";

    public String shortDescription();

    public String[] reviews();

}

Now let’s move on to the last annotation @Inherited. Inherited is probably the most complex of all annotations and does provide an interesting option. In a typical inheritance heirarchy, we define the parent as typically an interface and all the children implement that interface. In case we need to associate any default behavior with interface, we need to create an abstract class that implements the default behavior and all children inherit this class. Let’s use an example to understand this.

package com.test.annotations.inherited;

public interface Car {

    public String makeNoise();

}

package com.test.annotations.inherited;

public class AbstractCarImpl implements Car {

    public String makeNoise() {
	return "Vroom";
    }

}

package com.test.annotations.inherited;

public class BMW extends AbstractCarImpl {

}

package com.test.annotations.inherited;

public class Toyota extends AbstractCarImpl {

}

The AbstractCarImpl class has been created purely to ensure that the default behavior associated with Car i.e. makeNoise is implemented. This severely restricts the object hierarchy. The Inherited annotation may provide us an alternative. Note this has some restrictions.

package com.test.annotations.inherited;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Inherited
public @interface CarAnnotation {

    public String makeNoise() default "Vroom";

}

package com.test.annotations.inherited;

@CarAnnotation
public class BMWCar {

}

We define a custom annotation named CarAnnotation. Add the @Inherited annotation to it and add the annotation in the child class BMWCar. The advantage of this approach is that we do not need to define an intermediate abstract class for default implementations. The default implementations are inherited because of the @Inherited tag. The pitfalls are that this might take some time to understand and we cannot use this approach for complex data types. The data types are restricted to the annotation type restriction.

That’s all from the annotation desk at the moment.

Categories: Java annotations · custom annotation

6 responses so far ↓

Leave a Comment