Topic 100 of 119 12. Java Annotations Interactive Tutorial

Creating Custom Annotations in Java: Meta-Annotations and Reflection Processing

9 mins read Last updated: 22 Aug 2026 32,300 views

Direct Answer & Core Concept

Custom annotations allow Java developers to create their own metadata that can be attached to classes, methods, fields, constructors, and other program elements. They are useful when an application needs to describe code in a structured way without changing the actual business logic.

Java frameworks such as Spring, JUnit, Jakarta EE, and many testing or dependency-injection tools use annotations heavily. Understanding how to create custom annotations is therefore an important step toward advanced Java development.

In this tutorial, you will learn how to create custom annotations, control where they can be used with meta-annotations, retain them at runtime, and read them using Java Reflection.


Detailed Explanation & Mechanics

What Is a Custom Annotation in Java?

A custom annotation is an annotation defined by the developer for a specific application or framework requirement.

Instead of repeatedly storing configuration in comments or separate files, annotations can place metadata directly next to the code they describe.

For example:

Main.java
Run Online
@Developer("Admin")class UserService {
}

Here, @Developer can be a custom annotation created by the application.

The annotation itself does not automatically execute code. It simply provides metadata that another part of the program can read and process.


Why Use Custom Annotations?

Custom annotations are useful when you want to attach structured information to Java code.

Common use cases include:

  • Marking classes for framework processing

  • Identifying service or controller classes

  • Defining validation rules

  • Creating test metadata

  • Configuring logging behavior

  • Describing permissions

  • Registering application components

  • Controlling reflection-based processing

  • Building lightweight frameworks

For example:

Main.java
Run Online
@Role("ADMIN")class AdminService {
}

A security component could inspect this annotation and determine that the class is associated with the ADMIN role.


Basic Syntax of a Custom Annotation

A custom annotation is declared using the @interface keyword.

Main.java
Run Online
@interface Developer {
    String value();
}

You can then use it like this:

Main.java
Run Online
@Developer("Admin")class UserService {
}

The syntax may look similar to an interface, but an annotation declaration has a different purpose.

The @interface keyword tells Java that you are defining an annotation type.


Creating Your First Custom Annotation

Let's create a simple annotation called @Info.

Main.java
Run Online
@interface Info {
    String author();
    String version();
}

We can use it like this:

Main.java
Run Online
@Info(author = "Admin", version = "1.0")class PaymentService {
}

The annotation now stores two pieces of metadata:

  • author

  • version

However, we have not yet specified where this annotation can be used or how long it should remain available.

That is where meta-annotations become important.


What Are Meta-Annotations?

Meta-annotations are annotations that provide information about another annotation.

Java provides several important meta-annotations:

Meta-AnnotationPurpose
@TargetSpecifies where an annotation can be used
@RetentionSpecifies how long an annotation is retained
@DocumentedIncludes the annotation in generated documentation
@InheritedAllows certain class annotations to be inherited
@RepeatableAllows an annotation to be used multiple times

These annotations are commonly used when designing custom annotations.


The @Target Meta-Annotation

@Target controls which Java elements can use your custom annotation.

For example:

Main.java
Run Online
@Target(ElementType.TYPE)@interface ServiceInfo {
    String name();
}

This annotation can be applied to classes, interfaces, enums, and other type declarations.

Example:

Main.java
Run Online
@ServiceInfo(name = "Payment")class PaymentService {
}

But applying it to a method would not be allowed:

Main.java
Run Online
class PaymentService {
    @ServiceInfo(name = "process") void processPayment() {
    }
}

The compiler will reject this because the annotation was restricted to TYPE.


Common ElementType Values

Java provides several ElementType values.

Main.java
Run Online
ElementType.TYPEElementType.METHODElementType.FIELDElementType.CONSTRUCTORElementType.PARAMETERElementType.LOCAL_VARIABLEElementType.PACKAGEElementType.MODULEElementType.ANNOTATION_TYPEElementType.TYPE_USE

For example, a method-only annotation can be defined as:

Main.java
Run Online
@Target(ElementType.METHOD)@interface LogExecution {
}

Then:

Main.java
Run Online
class OrderService {
    @LogExecution void createOrder() {
        System.out.println("Order created");
    }
}

Allowing an Annotation on Multiple Elements

You can specify multiple targets using an array.

Main.java
Run Online
@Target( {
    ElementType.TYPE, ElementType.METHOD, ElementType.FIELD
})@interface Auditable {
}

Now the annotation can be used on classes, methods, and fields.

Example:

Main.java
Run Online
@Auditableclass User {
    @Auditable private String name;
    @Auditable void save() {
    }
}

The @Retention Meta-Annotation

@Retention determines how long an annotation is retained.

The three retention policies are:

Main.java
Run Online
RetentionPolicy.SOURCERetentionPolicy.CLASSRetentionPolicy.RUNTIME

They have different purposes.


RetentionPolicy.SOURCE

SOURCE means the annotation is available only in source code.

Main.java
Run Online
@Retention(RetentionPolicy.SOURCE)@interface DeveloperNote {
}

The annotation is discarded by the compiler and is not available in the compiled class at runtime.

This type of annotation is useful for tools that process source code.


RetentionPolicy.CLASS

CLASS means the annotation is stored in the compiled .class file but is generally not available through normal runtime reflection.

Example:

Main.java
Run Online
@Retention(RetentionPolicy.CLASS)@interface BuildInfo {
}

CLASS is the default retention policy if @Retention is not specified.


RetentionPolicy.RUNTIME

RUNTIME means the annotation remains available at runtime.

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)@interface Author {
    String name();
}

This is especially important when you want to process the annotation using Reflection.

For example:

Main.java
Run Online
@Author(name = "Admin")class ReportService {
}

A reflection API can inspect ReportService and read the @Author annotation.


@Retention(RUNTIME) and Reflection

If your application needs to inspect a custom annotation while the program is running, use:

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)

For example:

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@interface ComponentInfo {
    String name();
}

Now:

Main.java
Run Online
@ComponentInfo(name = "PaymentService")class PaymentService {
}

The annotation can be discovered using Reflection.


Reading a Custom Annotation Using Reflection

Java Reflection allows a program to inspect classes, methods, fields, constructors, and annotations at runtime.

Consider this annotation:

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@interface ComponentInfo {
    String name();
}

Apply it to a class:

Main.java
Run Online
@ComponentInfo(name = "PaymentService")class PaymentService {
}

Now inspect it:

Main.java
Run Online
public class Main {
    public static void main(String[] args) {
        Class<PaymentService> clazz = PaymentService.class;
        if (clazz.isAnnotationPresent(ComponentInfo.class)) {
            ComponentInfo info = clazz.getAnnotation(ComponentInfo.class);
            System.out.println(info.name());
        }
    }
}

Output:

The basic process is:

Main.java
Run Online
Class↓Check annotation↓Get annotation↓Read annotation values↓Process metadata

Using getAnnotation()

The getAnnotation() method retrieves a specific annotation.

Example:

Main.java
Run Online
ComponentInfo info = PaymentService.class.getAnnotation(ComponentInfo.class);

You can then read its values:

Main.java
Run Online
System.out.println(info.name());

This works because the annotation uses:

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)

Without runtime retention, normal reflection cannot retrieve the annotation at runtime.


Using isAnnotationPresent()

Before retrieving an annotation, you can check whether it exists.

Main.java
Run Online
Class<?> clazz = PaymentService.class;
if (clazz.isAnnotationPresent(ComponentInfo.class)) {
    System.out.println("Annotation found");
}

This is useful when the annotation is optional.


Custom Annotation with a Default Value

Annotation elements can have default values.

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@interface ComponentInfo {
    String name();
    String version() default "1.0";
}

Now you can write:

Main.java
Run Online
@ComponentInfo(name = "PaymentService")class PaymentService {
}

The version automatically becomes:

You can also override it:

Main.java
Run Online
@ComponentInfo( name = "PaymentService", version = "2.0")class PaymentService {
}

The Special value() Element

An annotation can have an element named value.

Main.java
Run Online
@interface Developer {
    String value();
}

This allows a shorter syntax:

Main.java
Run Online
@Developer("Admin")class UserService {
}

Instead of:

Main.java
Run Online
@Developer(value = "Admin")class UserService {
}

The shorter form works when value is the element being supplied and the annotation syntax permits that shorthand.


Creating a Method Annotation

Custom annotations can also describe methods.

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@interface LogExecution {
}

Use it:

Main.java
Run Online
class UserService {
    @LogExecution public void createUser() {
        System.out.println("Creating user");
    } public void deleteUser() {
        System.out.println("Deleting user");
    }
}

Now a reflection-based processor can find methods marked with @LogExecution.


Processing Method Annotations with Reflection

Example:

Main.java
Run Online
import java.lang.reflect.Method;
public class Main {
    public static void main(String[] args) {
        Method[] methods = UserService.class.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(LogExecution.class)) {
                System.out.println( "Logging method: " + method.getName());
            }
        }
    }
}

Possible output:

Main.java
Run Online
Logging method: createUser

This is a simple example of annotation processing at runtime.


Creating an Annotation with Multiple Properties

A custom annotation can contain several elements.

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@interface ApiEndpoint {
    String path();
    String method() default "GET";
    boolean authenticated() default true;
}

Use it:

Main.java
Run Online
class UserController {
    @ApiEndpoint( path = "/users", method = "GET", authenticated = true) public void getUsers() {
    }
}

Reflection can then read all these properties.

Main.java
Run Online
ApiEndpoint endpoint = method.getAnnotation(ApiEndpoint.class);
System.out.println(endpoint.path());
System.out.println(endpoint.method());
System.out.println(endpoint.authenticated());

This pattern is similar to how frameworks store configuration metadata.


@Documented Meta-Annotation

@Documented indicates that an annotation should be included in generated Java documentation when applicable.

Example:

Main.java
Run Online
import java.lang.annotation.Documented;
@Documented@interface ApiInfo {
    String value();
}

This is useful for annotations that are part of a public API and should appear in generated documentation.


@Inherited Meta-Annotation

@Inherited affects class-level annotations.

Example:

Main.java
Run Online
@Inherited@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@interface Feature {
    String value();
}

Now:

Main.java
Run Online
@Feature("Payment")class PaymentService {
}class OnlinePaymentService extends PaymentService {
}

When checking the subclass for the inherited class annotation:

Main.java
Run Online
Feature feature = OnlinePaymentService.class.getAnnotation(Feature.class);

the annotation can be inherited from the superclass.

Important: @Inherited applies to class inheritance. It does not make annotations on methods or fields automatically inherited.


@Repeatable Meta-Annotation

Sometimes the same annotation needs to appear multiple times on one element.

For example:

Main.java
Run Online
@Role("ADMIN")@Role("MANAGER")class UserService {
}

To support this, the annotation can be declared as repeatable.

First create a container annotation:

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@interface Roles {
    Role[] value();
}

Then define the repeatable annotation:

Main.java
Run Online
import java.lang.annotation.Repeatable;
@Repeatable(Roles.class)@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@interface Role {
    String value();
}

Now it can be used multiple times:

Main.java
Run Online
@Role("ADMIN")@Role("MANAGER")class UserService {
}

Reading Repeatable Annotations

You can retrieve all repeated annotations using:

Main.java
Run Online
Role[] roles = UserService.class.getAnnotationsByType(Role.class);

Then:

Main.java
Run Online
for (Role role : roles) {
    System.out.println(role.value());
}

Output:

This is useful when an element can have multiple independent metadata entries.


Complete Custom Annotation Example

Let's combine the important concepts into one practical example.

Step 1: Create the Annotation

Main.java
Run Online
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@interface ServiceInfo {
    String name();
    String version() default "1.0";
}

Step 2: Apply the Annotation

Main.java
Run Online
@ServiceInfo( name = "PaymentService", version = "2.1")class PaymentService {
    public void processPayment() {
        System.out.println("Payment processed");
    }
}

Step 3: Read the Annotation

Main.java
Run Online
public class Main {
    public static void main(String[] args) {
        Class<PaymentService> clazz = PaymentService.class;
        if (clazz.isAnnotationPresent(ServiceInfo.class)) {
            ServiceInfo info = clazz.getAnnotation(ServiceInfo.class);
            System.out.println("Service: " + info.name());
            System.out.println("Version: " + info.version());
        }
    }
}

Output:

Main.java
Run Online
Service: PaymentServiceVersion: 2.1

This demonstrates the complete annotation lifecycle:

Main.java
Run Online
Define↓Apply↓Compile↓Retain↓Discover with Reflection↓Read values↓Process metadata

Building a Simple Annotation Processor

A practical application can scan a class and automatically perform an action when it finds a specific annotation.

Consider:

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@interface Execute {
}

Create a service:

Main.java
Run Online
class TaskService {
    @Execute public void taskOne() {
        System.out.println("Task One");
    } public void taskTwo() {
        System.out.println("Task Two");
    }
}

Now create a processor:

Main.java
Run Online
import java.lang.reflect.Method;
public class AnnotationProcessor {
    public static void process(Object object) throws Exception {
        Method[] methods = object.getClass().getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(Execute.class)) {
                System.out.println( "Executing: " + method.getName());
                method.invoke(object);
            }
        }
    }
}

Run it:

Main.java
Run Online
public class Main {
    public static void main(String[] args) throws Exception {
        TaskService service = new TaskService();
        AnnotationProcessor.process(service);
    }
}

Output:

Main.java
Run Online
Executing: taskOneTask One

This shows how annotations can act as instructions for a reflection-based framework.


Reflection Methods Commonly Used with Annotations

Several Reflection methods are useful when processing annotations.

MethodPurpose
isAnnotationPresent()Checks whether an annotation exists
getAnnotation()Gets one specific annotation
getAnnotations()Gets annotations visible through the relevant API
getDeclaredAnnotations()Gets annotations directly declared on the element
getAnnotationsByType()Gets repeated annotations of a type
getDeclaredAnnotationsByType()Gets directly declared repeated annotations

For example:

Main.java
Run Online
if (method.isAnnotationPresent(LogExecution.class)) {
    LogExecution annotation = method.getAnnotation(LogExecution.class);
}

getAnnotations() vs getDeclaredAnnotations()

These methods can behave differently when inheritance is involved.

returns annotations that are visible according to the Reflection API's inheritance rules.

returns annotations directly declared on the element.

This distinction becomes particularly important when working with class-level annotations and @Inherited.


Annotation Processing vs Reflection

These two concepts are related but not identical.

Reflection

Reflection inspects classes and annotations at runtime.

Example:

Main.java
Run Online
method.getAnnotation(LogExecution.class);

Annotation Processing

Annotation processing generally refers to processing annotations during compilation using Java's annotation-processing APIs.

A compile-time processor can inspect source-level or compiled program elements and generate code, validate declarations, or produce other build-time output.

For example, a custom processor can generate classes based on annotations.

So:

Main.java
Run Online
Reflection → RuntimeAnnotation Processing → Commonly Compile Time

Do not assume every annotation processor uses Reflection. Compile-time annotation processing operates through Java's language/model APIs rather than ordinary runtime reflection.


When Should You Use Custom Annotations?

Custom annotations are especially useful when metadata needs to stay close to the code it describes.

Good use cases include:

  • Framework configuration

  • API metadata

  • Validation

  • Logging markers

  • Permission declarations

  • Test metadata

  • Serialization configuration

  • Dependency injection

  • Code generation

  • Plugin registration

For simple configuration, however, an annotation is not always necessary. A configuration file, normal Java object, or explicit method call may sometimes be easier to understand.


Common Mistakes with Custom Annotations

1. Forgetting Runtime Retention

This is one of the most common mistakes.

Main.java
Run Online
@interface MyAnnotation {
}

If you expect Reflection to find it at runtime, define:

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)

2. Using the Wrong @Target

If an annotation should only be applied to methods:

Main.java
Run Online
@Target(ElementType.METHOD)

Do not leave the target unrestricted unless that behavior is actually desired.


3. Assuming Annotations Execute Code

This does not automatically execute anything:

Main.java
Run Online
@Executevoid process() {
}

The annotation is metadata.

Some other component must read the annotation and decide what action to perform.


4. Confusing @Inherited with Method Inheritance

@Inherited applies to class annotations.

It does not automatically copy annotations from overridden methods into subclasses.


5. Using Reflection Everywhere

Reflection is powerful, but it can add complexity and runtime overhead.

For large systems, consider whether compile-time annotation processing or framework-supported mechanisms would be more appropriate.


Best Practices for Custom Annotations

Choose Clear Names

Prefer:

Main.java
Run Online
@Auditable@Cacheable@ApiEndpoint

over vague names such as:

when the purpose can be made more specific.

Define a Narrow Target

Use @Target to prevent accidental usage.

Choose Retention Carefully

Use RUNTIME only when runtime inspection is required.

Provide Sensible Defaults

Defaults can make annotations easier to use:

Main.java
Run Online
String version() default "1.0";

Keep Annotations Focused

An annotation should describe metadata rather than becoming a replacement for normal application logic.


Custom Annotations in Real Java Frameworks

Custom annotations are a foundation for many Java frameworks.

For example, frameworks may use annotations to identify:

Main.java
Run Online
Classes↓Methods↓Fields↓Configuration↓Framework processing

A framework can scan application classes, detect annotations, read their attributes, and register or configure components automatically.

This is one reason annotations are so important in modern Java development.


Custom Annotation Example for a REST API

Consider:

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@interface GetRoute {
    String path();
}

Use it:

Main.java
Run Online
class UserController {
    @GetRoute(path = "/users") public void users() {
        System.out.println("Users endpoint");
    }
}

A framework could scan controller methods and build a route table:

Main.java
Run Online
/users → UserController.users()

The annotation itself does not create the route. The framework's processing logic does that.


Custom Annotation Example for Authorization

You can define:

Main.java
Run Online
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@interface RequiresRole {
    String value();
}

Use it:

Main.java
Run Online
class AdminController {
    @RequiresRole("ADMIN") public void deleteUser() {
        System.out.println("User deleted");
    }
}

A security component could inspect the annotation before invoking the method.

Conceptually:

Main.java
Run Online
Request↓Find method↓Read @RequiresRole↓Check current user's role
↓
Allow or reject
↓
Execute method

Again, the annotation describes the requirement; the security mechanism enforces it.


Custom Annotations and Java Reflection: Key Relationship

The most important relationship to remember is:

Main.java
Run Online
Custom Annotation↓@Retention(RUNTIME)↓Compiled Class↓Reflection↓Annotation discovered↓Values read↓Application logic

If runtime reflection is part of your design, RetentionPolicy.RUNTIME is usually essential.


Interview Questions

What is a custom annotation in Java?

A custom annotation is a developer-defined annotation used to attach structured metadata to Java program elements.

How do you create a custom annotation?

Use the @interface syntax:

Main.java
Run Online
@interface MyAnnotation {
}

What does @Target do?

@Target specifies which Java program elements can use an annotation.

What does @Retention do?

@Retention specifies how long the annotation is retained: source code, class files, or runtime.

Which retention policy is needed for runtime reflection?

What does @Inherited do?

@Inherited allows certain class-level annotations to be inherited by subclasses when accessed through the relevant reflection APIs.

Can annotations contain methods?

Annotation elements look like method declarations:

They define metadata values rather than normal executable methods.

Can an annotation have default values?

Yes.

Main.java
Run Online
String version() default "1.0";

What is @Repeatable?

@Repeatable allows the same annotation type to be applied multiple times to the same declaration.

Do annotations execute code automatically?

No. Annotations provide metadata. Another mechanism, such as reflection-based code, a framework, or an annotation processor, must interpret that metadata.


Practice Example

Try creating your own @DeveloperInfo annotation.

Requirements:

Main.java
Run Online
Annotation name: DeveloperInfoTarget: TYPERetention: RUNTIMEProperties: name experience project

Then apply it:

Main.java
Run Online
@DeveloperInfo( name = "Admin", experience = 12, project = "Payment System")class Developer {
}

Finally, use Reflection to print the annotation values.

This small exercise combines the three most important concepts:

Main.java
Run Online
Custom Annotation+Meta-Annotations+Reflection

Suggested Image / Graphic Ideas

Graphic 1 — Custom Annotation Lifecycle

Main.java
Run Online
Define Annotation↓Apply Annotation↓@Target / @Retention↓Compile↓Reflection↓Read Metadata↓Application Logic

Graphic 2 — Java Meta-Annotations

Create a visual card showing:

Main.java
Run Online
@Target → Where?@Retention → How long?@Documented → Documentation?@Inherited → Class inheritance?@Repeatable → Multiple uses?

Graphic 3 — Reflection Processing

Main.java
Run Online
@ServiceInfo↓PaymentService.class↓getAnnotation()↓ServiceInfo object↓name() / version()↓Framework Logic

These graphics can make the tutorial easier for beginners to understand and provide useful visual content for the page.


Runnable Code Example: Creating Custom Annotations in Java

Java 17+
Main.java
Run Online
import java.lang.annotation.*;
import java.lang.reflect.Method;
//
1. Define Custom Annotation@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@interface AuditLog {
    String action() default "EXECUTE";
}//
2. Apply Custom Annotationclass PaymentService {
    @AuditLog(action = "CHARGE_CREDIT_CARD") public void charge(double amount) {
        System.out.println("Processing payment: $" + amount);
    }
}//
3. Process via Reflectionpublic class CustomAnnotationDemo {
    public static void main(String[] args) throws Exception {
        Method method = PaymentService.class.getMethod("charge", double.class);
        if (method.isAnnotationPresent(AuditLog.class)) {
            AuditLog audit = method.getAnnotation(AuditLog.class);
            System.out.println("Audit Action Intercepted: " + audit.action());
        }
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1

What keyword is used to declare a custom annotation in Java?

@interface.
Q2

What meta-annotation is required to make a custom annotation readable via reflection at runtime?

@Retention(RetentionPolicy.RUNTIME).

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Keep custom annotations focused on declarative metadata; pair them with clean Reflection/AOP processors for cross-cutting concerns.