Annotations in Java

Annotations in Java, we have all seen and used them in our code. Such examples are @Deprecated, @Override, @SuppressWarnings, @NonNull, @Nullable, etc. Most of us can say what these annotations imply or do, but how do they actually work? Before we discuss them in depth, let’s first understand what their purpose is.

What Are Annotations?

Annotations are simply metadata that we tie with our code. Meaning, extra data on top of the code we’re already writing. This metadata will be used either during compile or run time. There are three that Java includes, and they are: @Deprecated, @Override, @SuppressWarnings. Android also has its own large list of annotations on top of the three included in Java. You can find them here.

Annotations In Practice

Let’s take a quick look at one of the most common annotations every Java developer comes across: @Override. We’ll use the following Animal class as an example.

public abstract class Animal {
    private String name;

    public Animal(String name) { = name;

    abstract void makeNoise();

    public String getName() {
        return name;

    public void setName(String name) { = name;
public class Dog extends Animal {

    public Dog(String name) {

    void makeNoise() {

java override ide error

Almost all popular IDEs of have a way of automatically providing override methods. This is done as long as the class we are extending provides no implementation for said method (makeNoise()  is abstract). Furthermore, the @Override annotation is provided above makeNoise() as well. Its purpose is to state that this class is overriding a parent method. If we were to annotate a method with @Override that wasn’t a parent method, our IDE would show us an error similar to the one seen in the image below. 

The compiler would also throw an error if we tried to build this.

How does it really work?

Now you’re probably asking yourself how @Override is able to tell both the compiler and our IDE that this is in fact an error. Let’s take a look at the code.

public @interface Override {

Surprised right? The thing with annotations is that they contain no logic. There is nothing in this code snippet that is telling both our IDE and compiler to give us an error when improperly naming an override method. So then what’s really going on? How does this actually work? Think of annotations in this case as a marker. We mark a piece of code where extra logic needs to be applied by a consumer which “consumes” this annotation. For @Override, and the other annotations included in Java, this consumer is the JVM. The logic that will be applied is done at bytecode level in this case.

Writing our own annotations

Before we begin writing our own, we should first discuss four unique annotations which only exist for the use of creating other annotations. @Target, @Retention, @Documented, and @Inherited.

  • @Target: This annotation is used to denote where the annotation is supposed to go. For example, over a method, class, parameter, etc.
  • @Retention: Specifies at what level the annotation will be available. There are three options, source, class and runtime.
  • @Inherited: Sets whether or not (boolean) an annotation can be inherited from its super class. By default, this is false.
  • @Documented: An optional annotation to add if you wish for it to show up in generated java doc.

This is just a very brief description on all of these annotations. If you’d like more in-depth explanations, check out the oracle docs.

For this custom annotation example, let’s say we want to create an annotation that we can add to a method to determine its priority and the author responsible. It will look like the example below.

@AuthorPriority(author = "John", priority = AuthorPriority.Priority.HIGH)
public void rollOver(){


The code for creating this annotation will look like the following.

public @interface AuthorPriority {
    public enum Priority {LOW, MEDIUM, HIGH}
    String author() default "User";
    Priority priority() default Priority.LOW;

Since this is an annotation designated for methods, we use ElementType.METHOD for our @Target. We will also be writing a consumer for this annotation using reflection, so @Retention is set to RetentionPolicy.RUNTIME. You may have noticed that we set default values for our attributes. This is done by ending our attribute names with open and closed brackets, (), followed by the default keyword and finally a default value for the type (“User” for our author attribute). You should note however, that when specifying default values, if your annotation only contains one attribute, you must use value() instead of a unique name.

Writing a consumer

Being familiar with Java’s reflection is not necessary for this section. The code should make sense when reading it.

public static void main(String[] args) {
    Class dog = Dog.class;
    for(Method m : dog.getMethods()){
        AuthorPriority annotation = m.getAnnotation(AuthorPriority.class);
        if (annotation != null){
            System.out.println(m.getName() + " has the AuthorPriority annotation.");

Keep in mind this consumer is very basic and only serves as an example. For more on writing consumers, check out this great article by Hannes Dorfmann.

Liked the article? Share it!
Notify of

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Inline Feedbacks
View all comments