In 2005, with the arrival of Java 5, we became got to know new entities called annotations.

Annotations are a special form of syntactic metadata that can be declared in code. They are used to analyze code at compilation or at runtime. You can think of an annotation as a label, tag, or compiler hint.

You've probably come across annotations before. For example, when overriding a method of the parent class, we write @Override before the method itself. This annotation indicates that the parent's method will be overridden in the child class.

Syntax:

@Override
public int hashCode() {
      return super.hashCode();
}

I want to note right away that annotations don't only apply to methods. They are used with packages, classes, methods, fields and parameters.

To understand how annotations work, let's first get acquainted with the concept of a marker interface. Since the advent of Java, developers have always needed a way to mark classes in order to perform certain actions on them.

Prior to Java 5, they used an interface that did not do what we expect interfaces to do. It had no methods and no contract. It just marked a class as special in some way.

Such an interface was called a marker interface. From the name you might guess that its purpose is to mark classes for the JVM, compiler, or some library. Some marker interfaces, such as Serializable, remain. This marker interface lets us indicate that instances of a class can be serialized.

As we've seen, marker interfaces continue to live on even after the introduction of annotations.

Annotations versus marker interfaces:

@MyAnnotation
public class MyClass {}
public class MyClass implements MarkerInterface {}

Both approaches have the same objective, but there is a clear difference in their implementation. For example, consider an interface and an annotation that indicates that a class belongs to a particular type.

If we're using an interface, then we mark the class. If we use it incorrectly and an error occurs, then we'll discover the problem at compilation and the program won't run.

With annotations, everything is not so simple: here the error will be detected at runtime, which means that the program will start, but, unsurprisingly, it will not finish.

Note that if we need to mark a class for future use, its instances must be passed to a specific method:

public class MyInteger implements Sum {}
interface Sum {};

public static void main(String[] args) throws IOException {
        increase(new MyInteger());
}

public static void increase(Sum count) {
        // TODO
}

A marker interface works best here.

It's best to use annotations when we need something more, such as the parameters that annotations support.

Let's look at the standard annotations in the JDK:

Annotation Description Example
@Override Specifies that a method overrides a superclass's method or implements a method of an abstract class or interface.
@Override
public int hashCode() {
        return super.hashCode();
}
@Deprecated Marks code as deprecated.
@Deprecated
public abstract void method();
@SuppressWarnings Disables compiler warnings for the annotated element. Note that if you need to disable multiple categories of warnings, they must be enclosed in curly braces, for example @SuppressWarnings({"unchecked", "cast"}).
public class DocumentsFolder {
   private List documents;

   @SuppressWarnings("unchecked")
public void addDocument(String document) {
            documents.add(document);
   }
}

In this example, we are trying to add to a list that does not have a defined type (a generic type). The compiler will warn us about this. This is very useful, but sometimes there are too many "warnings" and they can be noisy. You can use this method annotation and specify a type of compiler warning as an argument. There are a lot of markers, so don't worry about remembering them all — IDEA will usually tell you which one to add.

Another example with multiple arguments:

@SuppressWarnings({"unchecked", "deprecated"})