Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 5

Custom Java Annotations: Definition and Application

Recommended resources

Introduction

Hello! In our last lesson, we delved into Java generics, seeing how they provide compile-time type safety and enable you to write reusable code, drawing a parallel to C++ templates but highlighting the key difference of type erasure.

Today, we shift our focus to another powerful feature that is absolutely central to the Spring framework: Java annotations. Your goal is to learn how to define and apply custom annotations.

Annotations are a form of metadata—data about your code—that you can embed directly into your source files. By themselves, they don't alter your code's logic. Their power comes from tools and frameworks that read this metadata at runtime and use it to configure behavior, generate code, or enable functionality. This is the "magic" behind Spring's dependency injection (@Autowired), web endpoint mapping (@GetMapping), and component detection (@Component).

In this lesson, we will pull back the curtain on this magic. You will not only create your own annotations but also write the code to process them, giving you a fundamental understanding of how modern frameworks are designed.


What Are Annotations?

At its simplest, an annotation is a label you attach to a part of your Java code, such as a class, method, or field. You've likely encountered some of Java's built-in annotations, like @Override, which instructs the compiler to verify that a method is correctly overriding a superclass method.

They serve three main purposes:

  1. Information for the compiler: Detect errors or suppress warnings (e.g., @Override, @Deprecated).
  2. Compile-time and deployment-time processing: Software tools can process annotations to generate code, XML files, etc.
  3. Runtime processing: Frameworks like Spring can inspect annotations at runtime to configure the application's behavior.

This last point is our primary focus, as it's the foundation of the Spring framework.

To begin, let's watch a short video segment that introduces the concept of annotations as metadata.

Annotations In Java Tutorial - How To Create And Use Your Own Custom Annotations

This video from 'Coding with John' offers a clear definition of what annotations are and shows a common built-in example. It explains that they are a form of metadata that can be processed by other tools.

Watch from 01:32 to 03:39. Pay attention to the explanation of annotations as 'supplemental information' and the example using @SuppressWarnings.


Defining Your First Custom Annotation

Creating a custom annotation is surprisingly simple. You declare it using the @interface keyword. However, to make an annotation useful, you must add meta-annotations that configure its behavior.

The two most important meta-annotations are:

  • @Target: Specifies where your annotation can be applied. Can it be on a class, a method, a field? You define this using ElementType.
  • @Retention: Specifies how long the annotation should be kept.
    • RetentionPolicy.SOURCE: Discarded by the compiler.
    • RetentionPolicy.CLASS: Included in the .class file but not available at runtime.
    • RetentionPolicy.RUNTIME: Available at runtime via reflection. This is the policy we need for building framework-like features.

Let's see how this is done in practice.

Annotations In Java Tutorial - How To Create And Use Your Own Custom Annotations

Now, let's see how to create our own custom annotation. The process is similar to creating an interface, but with a few key additions called 'meta-annotations' that control the annotation's behavior.

Continue watching from 03:39 to 07:40. Focus on: The @interface keyword for declaring an annotation. The @Target meta-annotation, which specifies where your annotation can be used (e.g., ElementType.TYPE for classes). The @Retention meta-annotation, and specifically why RetentionPolicy.RUNTIME is crucial for our purposes.

Here's the syntax for a basic annotation that can be applied to classes and is available at runtime:

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // Can be applied to classes, interfaces, enums
public @interface VeryImportant {
    // We can add elements (parameters) here later
}

And you would apply it to a class like this:

@VeryImportant
public class Cat {
    // ... class implementation
}

Processing Annotations with Reflection

An annotation is useless if nothing reads it. The mechanism for reading metadata at runtime is called Java Reflection. Reflection allows a program to inspect its own code—classes, methods, fields, and their annotations—and change its behavior accordingly. This dynamic, runtime nature is what makes frameworks like Spring so powerful and flexible.

Let's see how to write code that checks if a class has our @VeryImportant annotation.

Annotations In Java Tutorial - How To Create And Use Your Own Custom Annotations

An annotation doesn't do anything by itself. We need to write code that looks for these annotations and acts on them. This is done using Java's Reflection API. Let's see how to check for an annotation on a class.

Watch from 07:40 to 09:55. The key takeaway here is the isAnnotationPresent() method, which allows you to implement conditional logic based on whether a class has been annotated.

The core logic looks like this:

Cat myCat = new Cat();

if (myCat.getClass().isAnnotationPresent(VeryImportant.class)) {
    System.out.println("This class is very important!");
}

Adding Parameters to Annotations

To make annotations more versatile, you can add parameters, or "elements." These are declared as methods within the @interface definition. You can also provide default values to make them optional.

The following video segment demonstrates how to:

  1. Add parameters to an annotation.
  2. Provide default values.
  3. Access the parameter values at runtime using reflection.

Annotations In Java Tutorial - How To Create And Use Your Own Custom Annotations

Our annotations can be made more powerful by adding parameters. For example, we could specify how many times a method should be run. This video shows how to define these parameters and access their values at runtime.

Watch from 09:55 to 16:17. Note: How to declare a parameter (like int times()) inside the @interface definition, including a default value. How to retrieve the annotation object using getAnnotation() and then access its parameter values (e.g., annotation.times()). The example of processing an annotation on a method to invoke it a specific number of times.

Let's look at the syntax for an annotation with a parameter:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RunImmediately {
    int times() default 1; // An element named 'times' with a default value of 1
}

And the reflection code to process it:

// Assuming 'myCat' is an instance of a class with annotated methods
for (Method method : myCat.getClass().getDeclaredMethods()) {
    if (method.isAnnotationPresent(RunImmediately.class)) {
        RunImmediately annotation = method.getAnnotation(RunImmediately.class);
        int times = annotation.times();
        for (int i = 0; i < times; i++) {
            method.invoke(myCat); // Invokes the annotated method on the myCat object
        }
    }
}

This simple example is a microcosm of how frameworks operate: they scan your code for markers (annotations) and execute logic based on them.

How To Create Custom Annotation In Java?
This image provides a concise visual summary of the syntax for defining a custom annotation (`@Book`) with elements and default values, and then applying it to a class in various ways.

A More Practical Example: A Simple Validation Framework

To connect this to a real-world use case, let's examine how you could build a simple validation framework. This is highly relevant to building robust APIs, where input validation is critical.

The following article demonstrates creating annotations like @NotEmpty and @MinLength and then a Validator class that uses reflection to check an object's fields against the rules declared by the annotations.

Creating Custom Annotations in Java: A Developer's Guide

To solidify these concepts, let's walk through another practical example. This article demonstrates how to build a simple validation framework using custom annotations like @NotEmpty and @MinLength. This is a great illustration of how annotations can be used to create clean, declarative APIs.

Read the section titled 'Real-World Example: A Simple Validation Framework'. Study the definitions of the @NotEmpty and @MinLength annotations and then look at the Validator class to see how it uses reflection (getDeclaredFields, isAnnotationPresent, getAnnotation) to check the fields of an object against the rules defined by the annotations.

This validation example is very similar to how Spring's validation framework (@Valid, @NotNull, @Size) works under the hood, which you will use when building REST APIs.


The Spring Connection

Now you have all the pieces to understand how Spring works. Spring is, at its core, a sophisticated annotation processor.

  • When you write @Component on a class, Spring's classpath scanner (a reflection-based tool) finds it and creates an instance of that class, known as a "bean," in its IoC container.
  • When you use @Autowired on a constructor, Spring reflects on that constructor, finds the @Autowired annotation, and automatically supplies the required dependency beans from its container.
  • When you use @GetMapping("/users") on a method inside a @RestController class, Spring reflects on that method, reads the annotation's value ("/users"), and registers it in a map so that incoming HTTP GET requests to that path are routed to your method.

Understanding how to define and process annotations is the key to demystifying Spring and using it effectively.


Conclusion

In this lesson, you've learned how to create and process custom Java annotations, a fundamental skill for working with modern Java frameworks.

Here are the key takeaways:

  • Annotations are Metadata: They provide information about your code to the compiler or runtime environment without changing the code's direct execution.
  • Definition: You define a custom annotation using the @interface keyword.
  • Meta-Annotations: @Retention(RetentionPolicy.RUNTIME) is essential for runtime processing, and @Target controls where the annotation can be applied.
  • Processing: Java Reflection is the mechanism used to read annotations at runtime via methods like isAnnotationPresent() and getAnnotation(). This allows you to build dynamic, framework-like behavior.
  • Relevance to Spring: This entire mechanism is the engine that drives the Spring framework, from dependency injection to web request mapping.

In our next lesson, we will cover Java lambda expressions and functional interfaces. This is another critical, modern Java feature that enables a more concise and powerful way of writing code, especially when working with collections and asynchronous operations in Spring.