Java annotations provide meta information to source code. You probably have already seen and used some of them – like @Override for one.
There are many other predefined annotations, but sometimes they are not enough. Here we will look at how to create your own annotation and how to process it.

Defining a new annotation

Custom annotations have to be defined in @interface files. Let’s create a special annotation that represents class or method description with its author and version number. First, we define the @interface:

public @interface Description {}

Then there are a few things that can be specified in this class.

Retention policies

The first is annotation Retention. It specifies at which level your annotation will be applied. It is specified by @Retention annotation on your custom annotation class, like in the following snippet:

@Retention(RetentionPolicy.RUNTIME)
public @interface Description {}

There are 3 available retention types in Java:

SOURCE
These annotations are used by the compiler during the compilation process. For example, @Override annotation has this type of retention:

@Retention(RetentionPolicy.SOURCE)
public @interface Override { }

CLASS
It is the default retention. These annotations are recorded in the class file on compilation, but then they are not available during run time. It is basically used to perform byte code modifications. It can be used in code obfuscation or code generation libraries (e.g. Lombok). For example, @NotNull annotation has this type of retention:

@Retention(RetentionPolicy.CLASS)
public @interface NotNull { }

RUNTIME
Runtime annotations are also recorded in the class file, and then they can be read at run time. The @Deprecated annotation and @Retention itself has a runtime retention policy. This one is generally used for custom annotations because it is the only policy that can be processed manually during program execution.

Our custom @Description annotation will have it as well (check the construction):

@Retention(RetentionPolicy.RUNTIME)
public @interface Description {}

Target types

@Target tells where the annotation can be placed: methods, packages, annotations themselves, etc. You can look at the table to find out all the available values. If you don’t set up its value, the annotation will be applicable to all elements.

For our annotation we are going to use METHOD value:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Description {}

The following table shows all target types available for Java 1.8.

ElementType.ANNOTATION_TYPEAnnotations
ElementType.CONSTRUCTORConstructors
ElementType.FIELDFields
ElementType.LOCAL_VARIABLELocal variables
ElementType.METHODMethods
ElementType.PACKAGEPackages
ElementType.PARAMETERParameters
ElementType.TYPEClass, interface, enum, or annotation
ElementType.TYPE_PARAMETERType parameter declaration
ElementType.TYPE_USEUse of a type

Annotations parameters

Let’s add some parameters to our annotation that should specify different parts of our description. These parameters have restrictions on the type:

  • primitives;
  • String;
  • Class;
  • Enum;
  • annotation;
  • an array of these types.

Beware that the default value cannot be null. Now we can define our @Description annotation parameters:

  • description of the method itself;
  • author name;
  • version number.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Description {
    String author();
    String description();
    int version() default 0;
}

Additional meta-annotations

The annotation can be marked as @Repeatable and then it can be used multiple times at the same place. You should provide a container class name: the class with an array collecting repeatable annotations:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Descriptions {
    Description[] value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(Descriptions.class)
public @interface Description {...}

Now the annotation can be applied more than one time:

@Description(author = "John Doe", description = "first description")
@Description(author = "Richard Roe", description = "second description")
public void testMethod() {

@Documented takes no parameters and includes an annotation in the Javadoc documentation.

If the annotation is marked as @Inherited, then it will be applied to all subclasses of the annotated class.

Processing annotations

Now when we have defined our custom @Description annotation, we should also describe its processing. We want it to write the method description, author name, and version number to the console.

We will write a special class to process the annotation and construct the message to print method in it. As we made it @Repeatable, we need to retrieve container annotation @Descriptions first and then iterate over its @Description annotations:

public class DescriptionProcessor {

    public void printDescription(Object o) {
        // Get processing object class
        Class<?> processingClass = o.getClass();
        for (Method m : processingClass.getDeclaredMethods()) {
            // Check if method has container @Descriptions annotation
            if (m.isAnnotationPresent(Descriptions.class)) {
                // Get container annotation
                Descriptions descriptions = m.getAnnotation(Descriptions.class);
                StringBuilder sb = new StringBuilder();
                // Iterate over exact @Description annotations
                for (Description d : descriptions.value()) {
                    sb.append("Description: ")
                            .append(d.description())
                            .append(" Author : " )
                            .append(d.author())
                            .append(" Version : ")
                            .append(d.version())
                            .append("\n");
                }
                // Print result
                System.out.println(m.getName() + " Descriptions: ");
                System.out.println(sb.toString());
            }
        }
    }
}

Here we used java reflection to get the object class’s methods. Then we iterate them to find @Description annotated methods and create a message to be shown.

Now it’s testing time. Let’s create a test method and add an annotated method there:

public class TestClass {
    @Description(
            author = "John Doe", 
            description = "Testing method"
    )
    @Description(
            author = "Richard Roe", 
            description = "Repeatable description", 
            version = 1
    )
    public void testMethod() {
        System.out.println("The method to test the @Description annotation");
    }
}

And now we can test it in Main method:

public class Main {
    public static void main(String[] args) {
        // Creating processor object
        DescriptionProcessor processor = new DescriptionProcessor();
        // Creating test object with annotated method
        TestClass test = new TestClass();
        // Call processing method
        processor.printDescription(test);
    }
}

And look at console output:

testMethod Descriptions: 
Description: Testing method Author : John Doe Version : 0
Description: Repeatable description Author : Richard Roe Version : 1

Conclusion

Here you learned how to define custom annotation in Java:

  • define annotation retention (SOURCECLASS or RUNTIME) or use default one,
  • specify annotation target type or use default one,
  • create a container class if you want it to be @Repeatable,
  • decide if the annotation is @Documented or @Inherited,
  • write processing class using java reflection.

Leave a Reply

Your email address will not be published.