How to create your own annotations in Java

This is a practical tutorial, where you will learn how you can create your own custom annotation types in your Java program. But also an introduction into the Java annotation package, which can be useful for you.

In Java you have annotations such as "@Override", "@Id" and more. These are used to adjust the behavior of objects, functions and more.
But you can also create your own annotation (annotation type), where you can define what the annotation should do on the target (objects, functions, etc.) where it's called.
An annotation type is created through a special Java interface class with the identifier "@interface".

 

Annotation without an attribute

This annotation is called directly by its name.

Example:

public @interface MyCustomAnnotation {

}

You can use your created annotation type like this:

@MyCustomAnnotation
private String myVariable;

In this example the annotation does not have any attributes. But you can also do that, which we will see in the following sections.

 

 

Annotation with one attribute

This example initializes the current degrees of the city, which was passed in the attributes of this annotation. Only the part concerning the creation of annotations is shown in this tutorial.

public @interface CurrentDegreesCity {

	String cityName();

}

Annotation type must not be "public" like in this example. They can also have other access modifiers (levels) than "public".

This annotation can be called like this:

@CurrentDegreesCity(cityName="seoul")
private int deegresSeoul;

You can only pass one attribute. But if you pass a different amount of attributes, then you will get a compiler error. If you want that, then you have to extend the interface "CurrentDegreesCity" with an another method declaration.

An annotation type interface cannot have members that throw an exception or have a "void" return value. This will cause a compiler error.

WRONG:

public @interface CurrentDegreesCity {

	void cityName();

}

 

WRONG: 

public @interface CurrentDegreesCity {

	String cityName() throws IOException;

}

 

 

Annotations with attributes that contain several elements

You can also have attributes, where you can add an array as a value. This will allow to add several values to an attribute.


In this example we will pass an array of string, which contains the names of weekdays.

public @interface WeekdaysDegreesCity {

	String cityName();
	String[] weekdays();

}

This annotation can be called like this:

@CurrentDegreesCity(cityName="seoul", 
weekdays={ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" })
private int workingWeekDeegresSeoul;

 

If you pass only one value to the annotation, then you can also remove the curly brackets.
Example:

@CurrentDegreesCity(cityName="seoul", weekdays="Monday")
private int workingWeekDeegresSeoul;

 

 

Annotations with optional attributes

If you want to have attributes which can be added optionally, then you have to define a default value for that attribute.
Use identified "default" followed up by the default value.

Example:

public @interface CurrentWeatherCity {

String cityName() default "New York";

}

This annotation can be called like this:

@CurrentWeatherCity 
private String currentWeatherNewYork;

The default value "New York" will be used in this case.

 

 

Meta annotations

You can adjust the settings of your annotation type with this type of annotations. They are added on your created annotation interface.
These following meta annotations are available.

@Target

This defines where your annotation can be used. For example only on constructors, local variables, etc.

Example:

@Target( { FIELD, CONSTRUCTOR } )
public @interface CurrentWeatherCity {

}

In this example you can use this annotation only on "static" (class) variables and object variables.

All available values:

  • ANNOTATION_TYPE - Only on other annotations
  • TYPE - Only on all type declarations. Classes, Interfaces, Enums, etc.
  • CONSTRUCTOR - Only on constructors
  • METHOD - Only on static and non-static methods
  • FIELD - Only on static variables and object variables
  • PARAMETER - Only on parameter variables
  • LOCAL_VARIABLE - Only on local variables
  • PACKAGE - Only on package declarations

 

Annotations can be used everywhere by default. These values are defined in the class "ElementType".

If you want to pass only one value, then you must call this annotation like this:

@Target( value = FIELD )
public @interface CurrentWeatherCity {

}

Or like this:

@Target( ElementType.FIELD )
public @interface CurrentWeatherCity {

}

This must be also done on other meta annotations.

 

@Retention

This controls till when the annotations are available. The available values belong to the class "java.lang.annotation.RetentionPolicy".
Example:

@Retention( RetentionPolicy.RUNTIME )
public @interface CurrentWeatherCity {

}

In this example the annotation is available everywhere and also during runtime of the application.

This values are available:

  • SOURCE - Available only in the source code and it will be discarded by the compiler before the compiling process.
  • CLASS - Available in the class file. But it is not available in the runtime environment.
  • RUNTIME - Available in the class file and in the runtime environment. Annotation are available everywhere.

The default value is "CLASS".

 

@Documented

This means that this annotation will be mentioned in the documentation "Javadoc". Annotations are not included in the "Javadoc" by default.
Example:

@Documented
public @interface CurrentWeatherCity {

}

 

@Inherited

This means that the annotation for a super class can be inherited by the sub class.

This is used when the annotation is used on a class.
When that specific annotation is requested in the sub class and it does not exist there. Then the super class will be checked and the annotation from the super class will be returned.

Example:

@Inherited
@interface LoadWeather {
}

@LoadWeather
public class SimpleWeatherState {

}

 

@Repeatable

This means that the annotation can be repeated. You can make it possible that your annotation can be used multiple times on the same target (object, function, etc.) in a certain class.
Example:

@Repeatable(MyJavaClass.class)
@interface CurrentWeatherCity {
	String cityName() default "New York";
}

This annotation can be repeated like this in the class "MyJavaClass":

@CurrentWeatherCity(cityName="Seoul")
@CurrentWeatherCity(cityName="Vienna")

 

This was an introduction into creating your own annotations and the Java package "java.lang.annotation".


More about Java Annotations in Java 15:
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/annotation/package-summary.html


More about Java Annotations in Java 8:
https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/package-summary.html
https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Target.html