Creating annotations is a fairly simple process, although it is limited by some rules. Now we need to figure out what practical purpose they serve.

Let's recall how we create our own annotation.

We'll write an annotation that is for classes and methods and contains information about the code's author and version:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Info {
   String author() default "Author";
   String version() default "0.0";
}

Here are our classes that we annotated:

@Info
public class MyClass1 {
   @Info
   public void myClassMethod() {}
}

@Info(version = "2.0")
public class MyClass2 {
   @Info(author = "Anonymous")
   public void myClassMethod() {}
}

@Info(author = "Anonymous", version = "2.0")
public class MyClass3 {
   @Info(author = "Anonymous", version = "4.0")
   public void myClassMethod() {}
}

How can we use this data at runtime?

By using reflection to extract metadata from annotations. Recall what reflection is. Reflection is a mechanism for examining data about a program at runtime. Reflection lets you get information about fields, methods, class constructors, and classes.

We'll use reflection to read the annotations in a class and display the information we want.

We'll read data from our classes in the main method:

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;

public class Main {
   public static void main(String[] args) throws NoSuchMethodException {
       readMyClass(MyClass1.class);
       readMyClass(MyClass2.class);
       readMyClass(MyClass3.class);
   }

   static void readMyClass(Class<?> myClassObj) throws NoSuchMethodException {
       System.out.println("\nClass: " + myClassObj.getName());
       readAnnotation(myClassObj);
       Method method = myClassObj.getMethod("myClassMethod");
       readAnnotation(method);
   }

   static void readAnnotation(AnnotatedElement element) {
       try {
           System.out.println("Search for annotations in " + element.getClass().getName());
           Annotation[] annotations = element.getAnnotations();
           for (Annotation annotation : annotations) {
               if (annotation instanceof Info) {
                   final Info fileInfo = (Info) annotation;
                   System.out.println("Author: " + fileInfo.author());
                   System.out.println("Version: " + fileInfo.version());
               }
           }
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
}

We pass an instance of our class to the readMyClass method.

Then we can pass a class as well as a method to the readAnnotation method. Let's do that — we'll pass a Class object and a Method object. It takes an object that implements the AnnotatedElement interface. This lets us get a list of annotations from the object and get information about each of them.

Note that we only get information after checking whether the annotation belongs to our annotation type: if (annotation instanceof Info).

The program output gives us complete information from the annotations:

Class: annotation.MyClass1
Search for annotations in java.lang.Class
Author: Author
Version: 0.0
Search for annotations in java.lang.reflect.Method
Author: Author
Version: 0.0

Class: annotation.MyClass2
Search for annotations in java.lang.Class
Author: Author
Version: 2.0
Search for annotations in java.lang.reflect.Method
Author: Anonymous
Version: 0.0

Class: annotation.MyClass3
Search for annotations in java.lang.Class
Author: Anonymous
Version: 2.0
Search for annotations in java.lang.reflect.Method
Author: Anonymous
Version: 4.0

Thus, with the help of reflection, we were able to extract the metadata.

Now let's look at an example of using annotations to improve code, including increasing readability, speed, and quality in general. Lombok will help us with this.

Lombok is a compiler plugin that uses annotations to hide a huge amount of code and extend the language, thereby simplifying development and adding some functionality.

Consider an example of annotations from Lombok:

@ToString Generates an implementation of the toString() method that consists of a thorough representation of the object: the class name, all fields, and their values.
@ToString
public class Example
@EqualsAndHashCode Generates implementations of equals and hashCode that use non-static and non-static fields by default, but are configurable. More details can be found on the project's website. There you will find an example that uses @EqualsAndHashCode and also shows a standard implementation without the annotation.
@Getter / @Setter Generates getters and setters for private fields.
@Getter
@Setter
private String name = "name";
@NonNull Used to assert that fields are not null when an object is instantiated. Otherwise, a NullPointerException is thrown.
public Example(@NonNull P p) {
 super("Hello");
 this.name = p.getName();
}

Lombok has many more useful annotations that are used less often. We have considered its simplest annotations. You can read more about the project on the official website.