Annotation-Based Code Generator in Java - Java Annotations [2/4]

In this tutorial, we'll be delving into the world of Java annotations. I'll explain what annotations are, their significance, and guide you through creating your very own custom annotations. So, let's get started!

Introduction to Java Annotations

Annotations in Java serve as a means of conveying additional information about your code. They enhance readability, assist in documentation, and even influence the behavior of the code. To grasp this concept better, consider a scenario involving two classes: Student and CollegeStudent.


class Student {
    public void print() {
        System.out.println("Student");
    }
}

class CollegeStudent extends Student {
    public void pritn() {
        System.out.println("CollegeStudent");
    }
}

class Main {
    public static void main(String[] args) throws Exception {
        CollegeStudent student = new CollegeStudent();
        student.print();
    }
}

The CollegeStudent class extends the Student class and attempts to override a method (print). Unfortunately, a typo results in the incorrect method being invoked. The output of the following program is:


Student

While, the user intended to invoke print method of CollegeStudent. To prevent such errors, Java offers a built-in annotation, @Override. This annotation guarantees that a method genuinely intends to override another, minimizing human mistakes.


class Student {
    public void print() {
        System.out.println("Student");
    }
}

class CollegeStudent extends Student {
    @Override
    public void pritn() {
        System.out.println("CollegeStudent");
    }
}

class Main {
    public static void main(String[] args) throws Exception {
        CollegeStudent student = new CollegeStudent();
        student.print();
    }
}

The output of the modified version is


error: method does not override or implement a method from a supertype
    @Override
    ^
1 error

Built-in Annotations and Their Roles

Java provides an array of built-in annotations for various purposes. Examples include:

  • @Deprecated: Flags methods or classes designated for future removal.
  • @Test: Identifies methods as test methods for testing frameworks.

While there are numerous built-in annotations, our primary focus lies in crafting custom annotations with meaningful roles. If you are interested in knowing in detail about essential built-in annotations, please comment down below.

Crafting Your Custom Annotations

To create custom annotations, use the @interface keyword, similar to how you define classes or interfaces. You can also embed methods within annotations, which then serve as parameters for annotation users. For instance:


public @interface Test {
   String name() default " ";
}

In this example, the user can specify a test's name, which aids in identifying test outcomes. You can choose not to define any methods, resulting in a Marker annotation, much like @Override.

Methods within annotations are analogous to variables in Java—they require a data type, can have default values, don't accept parameters, and cannot throw exceptions.

Controlling Annotation Usage with @Target and @Retention

To dictate where users can apply your annotation (class, method, field, etc.), employ @Target, which operates with the ElementType enum. The ElementType enum can have the following values

ElementTypeWhere can be Applied?
TYPEClass, interface (including annotation interface), enum, or record declaration
FIELDField declaration (includes enum constants)
METHODMethod declaration
PARAMETERFormal parameter declaration
CONTRUCTORConstructor declaration
LOCAL_VARIABLELocal variable declaration
ANNOTATION_TYPEAnnotation interface declaration (Formerly known as an annotation type.)
PACKAGEPackage declaration
TYPE_PARAMETERType parameter declaration
TYPE_USEUse of a type
MODULEModule declaration.
RECORD_COMPONENTRecord component

Reference: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/annotation/ElementType.html

For controlling when annotations evaluate, Java offers @Retention with RetentionPolicy options like SOURCE, CLASS, and RUNTIME. The RetentionPolicy can have the following values

RetentionPolicyDescription
CLASSAnnotations are to be recorded in the class file by the compiler but need not be retained by the VM at run time.
RUNTIMEAnnotations will be evaluated at the run-time.
SOURCESuch annotations are evaluated during compile-time/build.

Reference: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/annotation/RetentionPolicy.html

Based on this knowledge, our three annotation classes will be


package com.gogettergeeks.annotation;

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

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface FileDBGenerated {

}

package com.gogettergeeks.annotation;

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

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface Persisted {
}

package com.gogettergeeks.annotation;

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

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface UniqueKey {
}

Implementing the Custom Annotation Processor

Since our project require evaluation during build process, we’re going to delve into SOURCE RetentionPolicy. To create a processor, create a class that extends javax.annotation.processing.AbstractProcessor. Specify the annotations your processor evaluates and the compatible Java version using @SupportedAnnotationTypes and @SupportedSourceVersion annotations. You also need to override the abstract method process, which will be invoked by compiler during build process. For example, see our FileDBGeneratedProcessor below


@SupportedAnnotationTypes("com.gogettergeeks.annotation.FileDBGenerated")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class FileDBProcessor extends AbstractProcessor {
   @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // Logic to generate annotation
    }
}

Once you are done with the implementation, you need to register your processor with Java, and it'll execute during build due to the source retention policy. Compile using the -p flag, e.g., javac -p <processor-path> <main-class>.java. We'll see each of these concepts in more details in the next post of this tutorial.

Wrapping Up

Congratulations on conquering the realm of Java annotations and custom annotation creation! Annotations elevate your code's organization, efficiency, and purpose. If you enjoyed this tutorial, don't hesitate to give it a thumbs up and share it with fellow developers.