Annotations are a kind of metadata attached to various parts of the source code in Java. Although they do not directly affect how the code works, they are processed and used by different tools to provide additional functionality or services to the application. The typical use cases are:
- instructions to the Java compiler – checking various assumptions (e.g. whether a method was correctly overridden), suppressing warnings, marking and reporting usage of deprecated code and more
- instructions to the environment – generating code, XML files and more
- runtime processing – taking different actions at runtime depending on the presence and the contents of the annotation
Annotations are used extensively in various frameworks to simplify source code, reduce the length of configuration, provide loose coupling between components and much more. The notable frameworks using annotations are Java EE, Spring, Hibernate and JUnit.
Basic usage
Annotation can be attached to a source code element (e.g. a class or a method) by placing its name preceded with an at sign character (@) before the element to annotate:
@Override void doAction() { ... }
In case of many annotations it is also possible to provide additional elements in parentheses:
@SuppressWarnings(value = "unchecked") void doAction() { ... }
If only one element named value is provided, its name may be omitted as shown below:
@SuppressWarnings("unchecked") void doAction() { ... }
If needed multiple annotations of different types may be attached to the same source code element:
@Entity @Table(name = "PEOPLE_PERSON") public class Person implements Serializable { ... }
If there are at least two annotations of the same type (also called repeating annotations), they have to be grouped into one composed annotation. In the code below, two different @NamedQuery annotations could not be attached to the same class so they were put as a part of @NamedQueries annotation:
@Entity @Table(name = "PEOPLE_PERSON") @NamedQueries({ @NamedQuery(name = "selectAllPersons", query = "select o from Person o"), @NamedQuery(name = "countAllPersons", query = "select count(o) from Person o") }) public class Person implements Serializable { ... }
Since Java 8 it is possible to use repeating annotations without explicit grouping but it requires a small change to a definition of each annotation type. Therefore, it may not be yet possible in many cases.
Common annotations
Java SE comes with few predefined annotations. Some of them used by the Java compiler are described below.
@Override
@Override annotation informs the compiler that given method is intended to override a method declared in a superclass. If the method with this annotation fails to override any method (e.g. due to incompatible signature), the Java compiler raises an error. Although using this annotation is not necessary (omitting it does not cause any compilation errors), it is very useful to detect possible issues when doing large modifications or refactoring.
class MyFile implements AutoCloseable { @Override public void close() throws Exception { ... } }
@Deprecated
@Deprecated annotation informs that given element (e.g. constructor, class, method) is deprecated and its usage is discouraged. When a deprecated element is used somewhere in the source code, a warning is reported during compilation. Many IDE provide also visual indication to a developer by striking through all deprecated elements and their usage.
@Deprecated annotation is closely related with JavaDoc @deprecated tag. If @Deprecated annotation is used, it is also a good idea to add @deprecated tag to a JavaDoc comment explaining why it was deprecated and what could be used in place of it.
/** * Closes the file. * @deprecated Does not report errors. * Use closeFile() instead. */ @Deprecated void close() { ... }
@SuppressWarnings
@SuppressWarnings annotation instructs the compiler to suppress given warnings for annotated elements and all of its children. At the moment there are only two official (described in the Java Language Specification) categories of warnings which can be suppressed:
- deprecation – suppresses warnings about usage of deprecated elements
- unchecked – suppresses warnings about usage of raw (unchecked) types
However, IDE (e.g. Eclipse or IntelliJ IDEA) and Java compilers can and usually do implement their own custom categories. For example you can get all categories supported by Oracle Java compiler using command:
$ javac -X
and searching for ‘-Xlint’ option.
Here is a sample code which suppresses warning regarding usage of deprecated LineNumberInputStream class:
@SuppressWarnings("deprecation") void read() { LineNumberInputStream is = new LineNumberInputStream(System.in); }
@SafeVarargs
@SafeVarargs annotation informs compiler that the annotated method or constructor does not do any potentially unsafe operations on its varargs arguments. This annotation is generally used to suppress unchecked warnings related to the usage of varargs in the code like this:
@SafeVarargs static <T> void print(T... args) { for (T t : args) System.out.println(t); } void callPrint() { print(new ArrayList<Integer>(), new ArrayList<Long>()); }
Because looking at the body of the print() method, we don’t see any dangerous casts or assignments, we can safely add @SafeVarargs annotation. Without @SafeVarargs the compiler would generate warnings similar to these:
warning: [unchecked] Possible heap pollution from parameterized vararg type T warning: [unchecked] unchecked generic array creation for varargs parameter of type ArrayList...
@FunctionalInterface
@FunctionalInterface annotation (available since Java 8) informs the Java compiler that given interface is intended to be a functional interface. The functional interface is an interface with exactly one abstract method (not counting default methods and abstract methods overriding methods of Object class) and which can be used together with lambda expressions. If the interface annotated with @FunctionalInterface contains different number of abstract methods than one, the Java compiler raises an error. Although using this annotation is not necessary (omitting it does not cause any compilation errors), it is very useful to detect possible issues when doing bigger modifications or refactoring.
In the example below removing existing method test() or adding a new abstract method (except the ones mentioned above) would result in a compilation error:
@FunctionalInterface public interface MyPredicate<T> { boolean test(T data); }
Annotations and marker interfaces
Marker interface is simply an interface without any methods. Although it does not define any behavior in a typical sense, it carries type information (information whether the class implements given marker interface or not) which can be used by various mechanisms in Java to perform some special handling. Two widely known examples of such interfaces are Serializable and Cloneable.
Most probably annotations would be better suited for marking the class as serializable or cloneable but they were not available in Java at the time serialization and cloning were introduced. Nowadays, we may think about marker interfaces as very limited and unattractive predecessors of annotations.
Conclusion
These days, annotations are used almost everywhere in Java so it is very important to know them. Although this article covers only the basic usage of few existing annotations, it should be enough to start using other annotations available in various frameworks.
Nice introduction of Annotations, would have been better if you explained about creating custom annotations as well. I agree that days of marker interface is long over in Java.