All About Annotations in Java
In this tutorial, we’ll learn basics of Java Annotations, Built-in Java Annotations, How to create single-value, multi-value, repeated and type annotations and How to use them at runtime.
Overview
Annotations are very powerful feature of Java. You must have come across @Override
, @Deprecated
, @SuppressWarnings
, which are built-in annotations in Java.
Most of the Java-based framework heavily use annotations. For instance, Spring Framework provides @Autowired
, @Controller
, @Component
, … . Similarly Hibernate Framework provides @Entity
, @Table
, @Column
, … . Annotations make these frameworks easier to use and at the same time make the source code more readable.
What are Annotations?
Annotations are used to add some marker or data at class, method, or field level. This data is used at compile-time or run-time to perform some actions.
It is important to note that Annotations doesn’t have any direct effect on the behavior of class, method, or field, where it is applied. For example, adding @Override
, @Deprecated
or @SuppressWarnings
at method level doesn’t change the output of that method.
However frameworks such as Spring and Hibernate use these annotations to perform some actions at runtime. For example, adding @Autowired
at field level instruct the Spring framework to inject the dependency of this field at runtime.
Let’s understand two important concepts of annotations now - Target and Retention Policy
Target
Target defines where the annotation can be applied for example, at class, method or field level. This information is required while creating an annotation. There are the types of possible targets:-
@Target(ElementType.*) | Annotation can be applied on … |
---|---|
ElementType.TYPE |
class, interface or enum |
ElementType.METHOD |
method |
ElementType.FIELD |
field |
ElementType.CONSTRUCTOR |
constructor |
ElementType.PARAMETER |
parameter of a method or constructor |
ElementType.PACKAGE |
package |
Note that some annotation like @Override
can be applied at method level only while others like @Deprecated
, @SuppressWarnings
can be applied at any level - class, method, or field.
Retention Policy
Retention policy defines the life-cycle of the annotation. This information is also required while creating an annotation. There are three types of retention policies:-
@Retention(RetentionPolicy.*) | Description |
---|---|
RetentionPolicy.SOURCE |
Annotations are retained only in source code but ignored in .class file by the compiler |
RetentionPolicy.CLASS |
Annotations are retained in .class file by the compiler but ignored by the JVM at run time. This is the default behavior |
RetentionPolicy.RUNTIME |
Annotations are retained in .class file by the compiler and also retained by the JVM at run time, so they can be used by java reflection |
Note that you normally use RetentionPolicy.RUNTIME
while creating your own custom annotation because you may want to use the data from your annotation at runtime using java reflection. We will talk more about it later in the post while creating custom annotations.
Java Built-in Annotations
Now we understand that What is Target and Retention Policy, which are two required information to create any annotation, Let’s look at the java built-in annotations:-
@Override
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
We see that @Override
annotation can be applied at methods only (target) and part of the source code only (retention policy).
@Override
public String toString() { }
When we use @Override
annotation at method level, it informs the complier that this method is meant to override a method declared in a superclass. It assures that method is overridden with correct signature. If not so, complier throws compilation error.
@Deprecated
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}
We see that @Deprecated
annotation can be applied anywhere such as class, field, method, constructor, package, or method arguments level. Also it is retained at runtime means information can be extracted from this annotation using java reflection at runtime.
@Deprecated
static void deprecatedMethod() { }
}
When we use @Deprecated
annotation at method level, it informs the compiler that this method is deprecated and should no longer be used. The compiler generates a warning whenever a program uses this method.
@SuppressWarnings
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
We see that @SuppressWarnings
annotation can be applied anywhere and retained in the source code only. We also see that this annotations takes comma separated String values. Few possible values are “rawtypes”, “unused”, “unchecked” and “deprecation”.
@SuppressWarnings({ "rawtypes", "unused", "unchecked", "deprecation" })
@SuppressWarnings("deprecation")
void useDeprecatedMethod() {
// suppress the deprecation warning while calling a deprecated method
objectOne.deprecatedMethod();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
void useRawCollection() {
// suppress the rawtypes warning when initialize non-parameterized (raw) ArrayList
// suppress the unchecked warning when adding an integer to raw ArrayList
ArrayList list=new ArrayList();
list.add(1);
}
When we use @SuppressWarnings
annotation at method level, it informs the compiler to suppress the warnings which it would generate otherwise.
How to create Custom Annotation?
Let’s create a custom Annotation @ClassInfo
- Annotations are created by using
@interface
, followed by annotation name which isClassInfo
in our case. - An annotation class can have one or more elements. They look like methods. For example in the below code, we have six elements namely
author
,date
,currentRevision
,lastModified
,lastModifiedBy
, andreviewers
. - Default values to element can be assigned using
default
keyword
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ClassInfo {
String author();
String date();
int currentRevision() default 1;
String lastModified() default "N/A";
String lastModifiedBy() default "N/A";
// Note use of array
String[] reviewers();
}
Meta-Annotations
You see that we have applied few annotations to our custom annotation ClassInfo
. These are called as meta-annotations, which provide additional information about our custom annotation. They are as follows:-
@Documented
indicates to include the@ClassInfo
annotation in JavaDoc. By default annotations are not included in JavaDoc.@Inherited
indicates to inherit the@ClassInfo
annotation in subclasses. By default annotations are not inherited.@Target(ElementType.TYPE)
indicates that@ClassInfo
annotation can be apply at class level only@Retention(RetentionPolicy.RUNTIME)
indicates that@ClassInfo
annotation elements can be accessed during runtime using java reflection.
How to use Custom Annotation?
Now we have created our custom annotation @ClassInfo
, Let’s use it on the class
@ClassInfo (
author = "John Doe",
date = "3/17/2002",
// Note array notation
reviewers = {"Alice", "Bob", "Cindy"}
)
class MyClass {
}
Note that we have skipped currentRevision
, lastModified
, lastModifiedBy
elements while using @ClassInfo
annotation. Default values are assigned to these elements.
Let’s see how to get the information from our custom @ClassInfo
annotation at runtime
public class AnnotationsTest {
public static void main(String[] args) {
printClassInfo(new MyClass());
}
public static void printClassInfo(Object object) {
Class<?> clazz = object.getClass();
if (clazz.isAnnotationPresent(ClassInfo.class)) {
ClassInfo classInfo = clazz.getAnnotation(ClassInfo.class);
System.out.println("author: " + classInfo.author());
System.out.println("date: " + classInfo.date());
System.out.println("currentRevision: " + classInfo.currentRevision());
System.out.println("lastModified: " + classInfo.lastModified());
System.out.println("lastModifiedBy: " + classInfo.lastModifiedBy());
System.out.println("reviewers: " + Arrays.toString(classInfo.reviewers()));
} else {
System.out.println("Class Info is not available");
}
}
}
Output
author: John Doe
date: 3/17/2002
currentRevision: 1
lastModified: N/A
lastModifiedBy: N/A
reviewers: [Alice, Bob, Cindy]
@Inherited
class MyChildClass extends MyClass {}
Remember, We applied @Inherited
meta-annotation on our custom annotation class ClassInfo
while creation. That means, @ClassInfo
info applied at MyClass
will be inherited by its child class MyChildClass
as well resulting both produce same output when passed in printClassInfo
method.
public static void main(String[] args) {
printClassInfo(new MyClass());
printClassInfo(new MyChildClass()); // same output as: new MyClass()
}
Repeated Annotation
Java 8 has introduced the Repeated annotation where same annotation can be used multiple times at class, method or field level. Before Java 8, this result into compilation error.
Let’s create @Author
annotation which can be used repeatedly at class level. It requires two steps:-
- First create a repeatable annotation
@Author
. We need to apply@Repeatable
meta-annotation on our custom annotation to use it multiple time. Note that we need to pass a container annotation class in@Repeatable
. - Second create a container annotation
@Authors
which holds all repeatableAuthor
values. This container annotation must have an element namedvalue()
and of type arrayAuthor[]
@Repeatable(Authors.class)
@interface Author {
String name();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Authors {
Author[] value(); //must have this signature
}
Now we have created our repeatable @Author
annotation. Let’s use it on MyClass
multiple times and get the list of all the authors at runtime.
@Author(name = "Author 1")
@Author(name = "Author 2")
@Author(name = "Author 3")
class MyClass {
}
Java 8 internally treats this Repeating Annotation as an instance of @Authors
holding an array of @Author
.
public class RepeatedAnnotationsTest {
public static void main(String[] args) {
System.out.println(getListOfAuthors(new MyClass()));
}
public static List<String> getListOfAuthors(Object object) {
Class<?> clazz = object.getClass();
if (clazz.isAnnotationPresent(Authors.class)) {
Author[] authors = clazz.getAnnotationsByType(Author.class);
return Arrays.stream(authors).map(author -> author.name()).collect(Collectors.toList());
} else {
System.out.println("Authors not available");
}
return null;
}
}
Output
[Author 1, Author 2, Author 3]
Type Annotation
Java 8 has introduced Type annotation by adding two new target types. Let’s use them to create @NotNull
annotation.
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
}
The ElementType.TYPE_PARAMETER
means the annotation can be applied on the declaration of a type variable (e.g., class MyClass
The ElementType.TYPE_USE
means the annotation can be applied on any use of a type (e.g., types appearing in declarations, generics, and type casts)
Let’s use the @NotNull
annotation on the type declaration of a field and use it to validate the null value. Throw exception if any field declared with @NotNull
annotation contains null value.
class MyClass {
private @NotNull String key;
}
public class TypeAnnotationsTest {
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
validateProperties(new MyClass()); // value of field key is null
}
public static void validateProperties(Object object) throws IllegalArgumentException, IllegalAccessException {
Class<?> clazz = object.getClass();
Field[] fields = clazz.getDeclaredFields();
for(Field field: fields) {
field.setAccessible(true);
if(field.getAnnotatedType().isAnnotationPresent(NotNull.class)) {
if(field.get(object) == null) {
throw new NullPointerException("@NotNull field \"" + field.getName() + "\" can not be null");
}
}
}
}
}
Output
Exception in thread "main" java.lang.NullPointerException: @NotNull field "key" can not be null
at com.example.core.TypeAnnotationsTest.validateProperties(TypeAnnotationsTest.java:14)
at com.example.core.TypeAnnotationsTest.main(TypeAnnotationsTest.java:4)
Some of the example usage of type annotations are as follows:-
// declaration
@Encrypted String data;
// generics
List<@NonNull String> strings;
// type cast
String myString = (@NonNull String) myObject;
// constructor
new @Interned MyObject();
new @NonEmpty @Readonly List<String>(myNonEmptyStringSet);
// implements clause
class UnmodifiableList<T> implements @Readonly List<T> {
...
}
// throw exception declaration
void monitorTemperature() throws @Critical TemperatureException {
...
}
Conclusion
We see that Java Annotations are widely used by Java and its popular framework such as Spring and Hibernate. Most of the time, we as a developer are consumers of these annotations, rather than their creator. Though it is good to have knowledge of how custom annotations are created and used behind the scene.