Creating Custom Annotations
- Authors
- Name
- Amit Shekhar
- Published on
Annotations are metadata, which means they are information about our code. In Java, we can create our own custom annotations using @interface and use them in our classes.
In this blog, we will learn how to create custom annotations in Java.
I am Amit Shekhar, Founder @ Outcome School, I have taught and mentored many developers, and their efforts landed them high-paying tech jobs, helped many tech companies in solving their unique problems, and created many open-source libraries being used by top companies. I am passionate about sharing knowledge through open-source, blogs, and videos.
I teach AI and Machine Learning, and Android at Outcome School.
Join Outcome School and get a high-paying tech job:
Let's get started.
Annotations are Metadata.
And Metadata is a set of data that gives information about other data.
So in this case, annotations are essentially just information about your code.
@Override
public String toString() {
return "I am Amit Shekhar";
}
Here, you have @Override annotation over the method toString(). Even if you don’t put the @Override annotation, this code will still work fine.
So, what is the use of @Override annotation here?
@Override tells the compiler that this method is an overridden method (metadata about the method) and whether any such method exists in its parent class. Then it throws a compiler error (the method does not override a method from its super class).
Look at the below code. It will not compile, as there is typo error: "toStrring" instead of "toString".
@Override
public String toStrring() {
return "I am Amit Shekhar";
}
But it will compile, if there is you don’t add an @Override annotation.
Creating custom annotations
For Example, Let’s create @Status annotation
Create a Status interface like below:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Status {
public enum Priority {LOW, MEDIUM, HIGH}
Priority priority() default Priority.LOW;
String author() default "Amit";
int completion() default 0;
}
A quick note for you
No matter which tech domain you work in, get familiar with these topics:
- LLM
- RAG
- MCP
- Agent
- Fine-tuning
- Quantization
We put it all together in one video:
AI Engineering Explained: LLM, RAG, MCP, Agent, Fine-Tuning, and Quantization
No need to stop reading - bookmark it and watch later when you get time. Future you will thank you.
Now, let's get back to the topic.
@Target specifies where an annotation can be placed. If you don’t specify this, the annotation can be placed anywhere. Here are some valid targets:
- ElementType.TYPE (class, interface, enum)
- ElementType.FIELD (instance variable)
- ElementType.METHOD
- ElementType.PARAMETER
- ElementType.CONSTRUCTOR
- ElementType.LOCAL_VARIABLE
@Retention defines how long the annotation should be kept around. Here are some valid retention policies:
- RetentionPolicy.SOURCE - Discard during the compile step. These annotations don’t make any sense after the compilation has completed, so they don’t need to be turned into bytecode. Examples: @Override, @SuppressWarnings
- RetentionPolicy.CLASS - Discard during the class load. Useful when doing bytecode-level post-processing. Somewhat surprisingly, this is the default.
- RetentionPolicy.RUNTIME - Don’t discard. This annotation should be available for reflection at runtime.
Using annotations in your Class Foo.java:
@Status(priority = STATUS.Priority.MEDIUM, author = "Amit Shekhar", completion = 0)
public void methodOne() {
//no code
}
@Status(priority = STATUS.Priority.HIGH, author = "Amit Shekhar", completion = 100)
public void methodTwo() {
//complete code
}
Now you can print the status of all the methods like below:
Class foo = Foo.class;
for(Method method : foo.getMethods()) {
Status statusAnnotation = (Status)method.getAnnotation(Status.class);
if(statusAnnotation != null) {
System.out.println(" Method Name : " + method.getName());
System.out.println(" Author : " + statusAnnotation.author());
System.out.println(" Priority : " + statusAnnotation.priority());
System.out.println(" Completion Status : " + statusAnnotation.completion());
}
}
If you have only one attribute inside an annotation, it should be named "value" and can be used without attribute name while using it:
@interface Status{
int value();
}
@Status(50)
public void someMethod() {
//few codes
}
And that’s how you create custom annotations.
To master Android internals, check out the Android Program by Outcome School.
Frequently Asked Questions
Is the @Override annotation required for the code to compile?
No. The code works fine without @Override. But with @Override, the compiler checks whether such a method exists in the parent class. So, a typo like "toStrring" instead of "toString" gives a compiler error only when the annotation is present.
What is the default retention policy of an annotation?
The default retention policy is RetentionPolicy.CLASS. With it, the annotation is discarded during the class load. It is useful when doing bytecode-level post-processing.
Which retention policy do we need to read an annotation using reflection?
We need RetentionPolicy.RUNTIME. With it, the annotation is not discarded and is available for reflection at runtime. With RetentionPolicy.SOURCE, the annotation is discarded during the compile step, like @Override and @SuppressWarnings.
What happens if we do not specify @Target on an annotation?
If we don't specify @Target, the annotation can be placed anywhere. With @Target, we limit where it can be used, for example only on methods with ElementType.METHOD.
Can we use an annotation without writing the attribute name?
Yes, when the annotation has only one attribute and it is named "value". Then we can write @Status(50) instead of writing the attribute name.
That's it for now.
Thanks
Amit Shekhar
Founder @ Outcome School
You can connect with me on:
Follow Outcome School on:
Read all of our high-quality blogs here.
Subscribe to our newsletter to get our latest AI and Machine Learning blogs straight to your inbox.
