What Are Java Annotations?
Java Annotations, sometimes referred to as "decorators", serve as a type of metadata applied to various Java constructs, including classes, methods, or fields. Although Java annotations themselves lack the ability to execute any logic, the Java Runtime Environment or the Java Compiler can leverage them to carry out specific actions or signal the need for particular operations, which are often delegated to another service or framework.
Annotations provide a hint about their intended purpose or the actions they trigger. For example, one annotation you should be quite familiar with is @Override.
Override Annotation
@Override is an annotation that comes with the Java libraries. It indicates that this method "overrides" a method in a parent class.
If someone accidentally changes this method signature or the method signature in the parent class, the compiler will throw an error. Here, the annotation simply tells the compiler it needs to verify that the two method signatures (from the parent and overriding child method) match exactly. If they do not, @Override informs the compiler to throw an error.
Java Annotation Usages
- Compiler Guidance — Annotations assist the compiler in error detection and warning suppression.
- Processing at compile-time and deployment — Software tools can utilize annotation details for code generation, XML file creation, and similar processes.
- Runtime Examination — Certain annotations are accessible for examination during runtime.
While it's true that some annotations are processed only at compile time and don't have a direct impact on the runtime behavior of the program (like @Override), there are annotations like those used in frameworks such as Spring that do affect the runtime behavior.
Spring Boot Annotations
For example, in Spring, annotations like @Component or @Autowired influence the creation and wiring of beans during the application's runtime.
You've encountered the @Autowired annotation in previous lessons. When you use the @Autowired annotation on a field (field dependency injection) or on a setter (setter dependency injection), it is the Spring framework, not the Java compiler, that processes this annotation for dependency injection at runtime.
While the annotation itself does not execute code, the Spring framework interprets annotation as a directive to perform dependency injection on the annotated fields or setters, thus influencing the behavior of the application at runtime. The @Autowired annotation acts as a metadata provider or a "marker" informing Spring to inject the appropriate dependency in the desired location.
Spring, along with many other frameworks, tools, and libraries, offers a plethora of useful annotations for developers. Moreover, creating custom annotations is a straightforward process. Before diving into a practical example, let's take a closer look at the fundamental components of an annotation.
Annotation Components
Java + Spring Boot annotations consist of two basic pieces of information:
-
Retention Policy - How long (program life-cycle) the annotation should be retained for.
-
Target - In which Java constructs (class/method/field) the annotation has been applied.
Retention Policy
Below, you can see that there are three types of retention policies - SOURCE, CLASS, and RUNTIME that you can leverage according to your requirements.
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the
* default behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler
* and retained by the VM at run time, so they may be read
* reflectively.
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
Target
Target takes an ElementType[] array as input, meaning you can specify more than one Java construct from the ElementType enum below. This will make more sense in just a moment.
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE,
MODULE
}
Custom Annotations
It's time to create and process a simple, custom annotation yourself.
Example Location:com.codingnomads.corespring.examples.annotations.whatandwhy
Your use case: Let's assume you have an AnnotationDemoService that implements the interface LegacyInfoProvider. Now, you want to change the result of the method info(), but you can't simply change the interface because lots of libraries are using this LegacyInfoProvider. You accomplish this by using a custom-created annotation ModernInfo.
Tip: This is a very realistic use case in real-life software development!
public class AnnotationDemoService implements LegacyInfoProvider {
@Override
public String info() {
return "legacy api fetching information";
}
}
Now, to create the custom annotation:
// here you are creating an annotation named "ModernInfo"
// the target type is "method", the retention policy is "runtime"
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ModernInfo {
String info() default "modern api information return";
}
Notice that the target is METHOD. And, since you want to use this annotation at runtime, you indicate the retention policy as RUNTIME.
Now, add your new annotation to the AnnotationDemoService.
public class AnnotationDemoService implements LegacyInfoProvider {
@ModernInfo
@Override
public String info() {
return "legacy api fetching information";
}
}
And the bootstrapping class:
public class AnnotationParsingDemo {
public static void main(String[] args) {
try {
Class<AnnotationDemoService> annotationDemoService =
AnnotationDemoService.class;
for (Method method : annotationDemoService.getMethods()) {
if (method.isAnnotationPresent(ModernInfo.class)) {
ModernInfo modernInfo =
method.getAnnotation(ModernInfo.class);
System.out.println("Info Received: " +
modernInfo.info());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Which produces the following output:
Info Received: modern api information return
Why Use Annotations in Java?
Java annotations serve various purposes, including:
Compiler Instruction
Java has three built-in annotations that give instructions to the Java Compiler, and you have already seen them many times. These are @Deprecated, @Override, @SuppressWarnings.
-
@Deprecatedcan be used to mark a class, a method, or a field. Many libraries use this annotation to propagate the message to their users that this class/method/field is going to be removed in a later version of this software and/or it is not safe/wise to use this class/method/field anymore. Most of the time, they will give a hint of which one to use instead of this (deprecated) construct. -
@Overrideis used to indicate that a subclass is overriding a method in a superclass.
For example:
public abstract class Bird {
abstract String fly();
}
public class Eagle extends Bird {
@Override
public String fly (){
System.out.println("Eagle is flying!!");
}
}
Since the Bird is an abstract class, the subclass Eagle must implement the method fly(). Using @Override confirms that the Eagle’s fly method is intended to override a method in the superclass.
- The
@SuppressWarningsannotation provides instruction to the compiler to not give any warning. For example, normally, when a method calls a deprecated method or performs an insecure type-casting operation, the compiler will throw a warning about that. This annotation gives a signal to the compiler saying that the user knows what they are doing, so don't bother about throwing any warnings for this particular construct/operation.
Build Time Instruction
Java annotations play a crucial role in guiding build tools like Gradle, Maven, or Ant. These tools scan your Java code for specified annotations, leading to the generation of source code or other files. This is a helpful and common practice.
Runtime Instruction
In the example above, you created an annotation that allows you to adjust a method result at runtime. In the Spring framework, there is extensive use of runtime instructions (RetentionPolicy.RUNTIME). You will learn about most of them in upcoming sections.
Learn by Doing
Package: corespring.examples.annotations.whatandwhy
- Inside the LegacyInfoProvider interface, add a
data()method that returns a String. - Create another custom annotation called SecondaryData in which data() returns "secondary data return".
- In AnnotationDemoService, override data() with a return of "primary data" and add your
@SecondaryDataannotation. - In AnnotationParsingDemo, modify the
ifstatement to check for this new annotation and call its data() method if present.
Please be sure to push your work to GitHub when you're done.
Summary: Java + Spring Boot Annotations
- Java Annotations are a form of metadata (aka "decorators") that are applied to Java classes, methods, or fields (aka Java "constructs").
@Overrideis an annotation that comes with the Java libraries. It indicates that this method "overrides" a method in a parent class.
Java annotations consist of two basic pieces of information:
- Retention Policy - How long (program life-cycle) the annotation should be retained for.
- Target - In which Java Constructs (class/method/field) the annotation has been applied.