Java Annotations
Run each complete program separately. Its required helper classes and imports are included in the same code block. Short syntax fragments explain individual operations.
An annotation adds metadata to a Java declaration or type use. A compiler, processor, framework, or runtime tool decides what that metadata means. Writing an annotation does not automatically run application logic.
Annotations are widely used in modern Java development and frameworks such as Spring, JUnit, Hibernate, and many other libraries.
For example:
@Override
public String toString() {
return "Hello";
}Here, @Override is an annotation. It tells the compiler that the method is intended to override a method from its parent class.
What is an Annotation in Java?
An annotation is a special form of metadata that starts with the @ symbol.
Syntax
@AnnotationNameExample
@OverrideAnnotations can also contain values:
@SuppressWarnings("unchecked")Or multiple values:
@Example(name = "Java", version = 21)Simple Definition
An annotation provides additional information about program elements such as classes, methods, fields, constructors, and parameters.
Annotations themselves generally do not directly execute business logic. Instead, the compiler, JVM, development tools, or frameworks can use the information provided by annotations.
Why Are Annotations Used?
Annotations are commonly used for:
- Providing information to the compiler
- Generating compiler warnings
- Marking deprecated code
- Configuring frameworks
- Runtime processing
- Testing
- Dependency injection
- Mapping objects to database tables
- Defining configuration
- Code generation
For example, Spring uses annotations such as:
@Component
@Service
@Repository
@AutowiredJUnit uses annotations such as:
@Test
@BeforeEach
@AfterEachBasic Example
Consider:
class Animal {
void sound() {
System.out.println("Animal makes sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}Here:
@Overrideis an annotation.
It tells the compiler:
This method is intended to override a method from the superclass.
Output
Dog barksTypes of Annotations
Java annotations can broadly be classified into:
- Predefined annotations
- Meta-annotations
- Custom annotations
1. Predefined Annotations
Java provides several built-in annotations.
Some commonly used ones are:
| Annotation | Purpose |
|---|---|
@Override | Indicates method overriding |
@Deprecated | Marks an element as deprecated |
@SuppressWarnings | Suppresses compiler warnings |
@FunctionalInterface | Indicates a functional interface |
@SafeVarargs | Suppresses certain varargs warnings |
@Repeatable | Allows an annotation to be repeated |
@Override
@Override indicates that a method is intended to override a method from a superclass or implement a method from an interface.
Example
class Animal {
void sound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}Why use @Override?
It helps the compiler detect mistakes.
For example:
class Dog extends Animal {
@Override
void sounds() {
System.out.println("Dog barks");
}
}If sounds() does not actually override a parent method, the compiler reports an error.
Without @Override, such a typo could accidentally create a new method.
@Deprecated
@Deprecated marks a class, method, field, or other program element as no longer recommended for use.
Example
class Calculator {
@Deprecated
public void oldMethod() {
System.out.println("Old method");
}
public void newMethod() {
System.out.println("New method");
}
}Using:
Calculator calculator = new Calculator();
calculator.oldMethod();can cause a compiler or IDE warning indicating that the method is deprecated.
Deprecated for Removal
Modern Java also supports:
@Deprecated(forRemoval = true)This communicates that the API is intended to be removed in a future release.
@SuppressWarnings
@SuppressWarnings tells the compiler to suppress specific warnings.
Example
import java.util.ArrayList;
import java.util.List;
class Demo {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
List list = new ArrayList();
list.add("Java");
String value = (String) list.get(0);
System.out.println(value);
}
}Output
JavaThe annotation does not fix the underlying issue. It only tells the compiler not to report the specified warning.
Therefore, it should be used carefully.
@FunctionalInterface
@FunctionalInterface indicates that an interface is intended to contain exactly one abstract method.
Example
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}This can be used with a lambda expression:
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
public class HelloWorld {
public static void main(String[] args) {
Calculator calculator = (a, b) -> a + b;
System.out.println(calculator.calculate(10, 20));
}
}Output
30If another abstract method is added:
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
int multiply(int a, int b);
}the compiler reports an error because the interface is no longer a functional interface.
2. Meta-Annotations
Meta-annotations are annotations used to define or configure other annotations.
Some important meta-annotations are:
@Target@Retention@Documented@Inherited@Repeatable
@Target
@Target specifies where an annotation can be applied.
For example:
@Target(ElementType.METHOD)
@interface MyAnnotation {
}This means @MyAnnotation can only be used on methods.
Example
class Demo {
@MyAnnotation
void show() {
System.out.println("Hello");
}
}The annotation cannot be applied to a class if its target is restricted to methods.
ElementType
ElementType defines the program elements where an annotation can be used.
Common values include:
| ElementType | Used On |
|---|---|
TYPE | Class, interface, enum, annotation |
FIELD | Field |
METHOD | Method |
PARAMETER | Method parameter |
CONSTRUCTOR | Constructor |
LOCAL_VARIABLE | Local variable |
ANNOTATION_TYPE | Annotation declaration |
PACKAGE | Package |
TYPE_PARAMETER | Type parameter |
TYPE_USE | Type usage |
Example
@Target({
ElementType.TYPE,
ElementType.METHOD
})
@interface Info {
}Now the annotation can be applied to both classes and methods.
@Retention
@Retention defines how long an annotation should be available.
There are three retention policies:
RetentionPolicy.SOURCE
RetentionPolicy.CLASS
RetentionPolicy.RUNTIMESOURCE
The annotation is available only in the source code.
It is discarded by the compiler and is not available in the compiled .class file.
Example
@Retention(RetentionPolicy.SOURCE)
@interface MyAnnotation {
}This type of annotation can be useful for source-code tools.
CLASS
The annotation is stored in the compiled .class file but is generally not available through normal runtime reflection.
Example
@Retention(RetentionPolicy.CLASS)
@interface MyAnnotation {
}CLASS is the default retention policy if no @Retention is specified.
RUNTIME
The annotation is retained at runtime and can be accessed through reflection.
Example
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
}Runtime annotations are especially important for frameworks.
For example, a framework can inspect a class and check whether a particular annotation is present.
Retention Policy Comparison
| Policy | Source Code | .class File | Runtime Reflection |
|---|---|---|---|
SOURCE | Yes | No | No |
CLASS | Yes | Yes | Generally no |
RUNTIME | Yes | Yes | Yes |
Creating a Custom Annotation
Java allows developers to create their own annotations.
The basic syntax is:
@interface AnnotationName {
}Example
@interface MyAnnotation {
}Usage:
@MyAnnotation
class Student {
}Custom Annotation with @Target and @Retention
A more useful custom annotation can be defined like this:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Info {
}Now:
@Info
class Student {
}The annotation can be detected at runtime.
Annotation with a Value
An annotation can contain elements.
Example
@interface Author {
String name();
}Usage:
@Author(name = "John")
class Student {
}Here:
name()is an annotation element.
Annotation Elements with Default Values
Annotation elements can have default values.
Example
@interface StudentInfo {
String name();
int age() default 18;
}Now we can write:
@StudentInfo(name = "John")
class Student {
}The value of age will be:
18We can also explicitly provide it:
@StudentInfo(name = "John", age = 20)
class Student {
}Annotation Element Rules
Annotation elements have restrictions on their types.
They can commonly use:
- Primitive types
StringClass- Enum
- Annotation types
- Arrays of supported annotation element types
Example
@interface EmployeeInfo {
String name();
int age();
Class<?> type();
String[] skills();
}Usage:
@EmployeeInfo(
name = "John",
age = 25,
type = String.class,
skills = {"Java", "Spring"}
)
class Employee {
}Single-Value Annotation
If an annotation has a single element named value, the element name can be omitted.
Example
@interface Author {
String value();
}Instead of:
@Author(value = "John")we can write:
@Author("John")This is a common annotation pattern.
Reading Annotations Using Reflection
Annotations become particularly useful when they can be inspected at runtime.
Consider:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface Author {
String name();
}Apply it to a class:
@Author(name = "John")
class Student {
}Now we can read the annotation using reflection.
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface Author {
String name();
}
@Author(name = "John")
class Student {
}
public class HelloWorld {
public static void main(String[] args) {
Class<Student> clazz = Student.class;
Author author = clazz.getAnnotation(Author.class);
System.out.println(author.name());
}
}Output
JohnComplete Custom Annotation Example
Let's create a custom annotation that stores information about an employee.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface EmployeeInfo {
String name();
String department();
int experience();
}
@EmployeeInfo(
name = "John",
department = "Development",
experience = 5
)
class Employee {
}
public class HelloWorld {
public static void main(String[] args) {
Class<Employee> clazz = Employee.class;
EmployeeInfo info =
clazz.getAnnotation(EmployeeInfo.class);
System.out.println("Name: " + info.name());
System.out.println("Department: " + info.department());
System.out.println("Experience: " + info.experience());
}
}Output
Name: John
Department: Development
Experience: 5How it works
The annotation:
@EmployeeInfo(...)stores metadata on the Employee class.
Because we used:
@Retention(RetentionPolicy.RUNTIME)the metadata remains available at runtime.
Then:
clazz.getAnnotation(EmployeeInfo.class)retrieves the annotation.
Annotation on a Method
Annotations can also be applied to methods.
Example
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Execute {
}Use it:
class Task {
@Execute
public void start() {
System.out.println("Task started");
}
public void stop() {
System.out.println("Task stopped");
}
}Now reflection can find methods annotated with @Execute.
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.reflect.Method;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Execute {
}
class Task {
@Execute
public void start() {
System.out.println("Task started");
}
public void stop() {
System.out.println("Task stopped");
}
}
public class HelloWorld {
public static void main(String[] args) {
Method[] methods = Task.class.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Execute.class)) {
System.out.println(
"Execute method: " + method.getName()
);
}
}
}
}Output
Execute method: startThis technique is conceptually similar to how frameworks discover specially marked methods.
@Inherited
@Inherited allows a class-level annotation to be inherited by subclasses.
Example
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Company {
String name();
}Apply it to a parent class:
@Company(name = "ABC")
class Parent {
}Subclass:
class Child extends Parent {
}Now:
Company company =
Child.class.getAnnotation(Company.class);
System.out.println(company.name());Output
ABCWithout @Inherited, class-level annotation inheritance does not occur through this mechanism.
Important
@Inherited applies to class annotations. It does not mean that annotations on methods and fields automatically become inherited.
@Documented
@Documented indicates that an annotation should appear in generated Java documentation when appropriate.
Example
@Documented
@interface DeveloperInfo {
String name();
}It is mainly useful when creating APIs or libraries where annotation information should be visible in generated documentation.
@Repeatable
@Repeatable allows the same annotation to be applied multiple times to the same declaration.
Example
@Repeatable(Roles.class)
@interface Role {
String value();
}Container annotation:
@interface Roles {
Role[] value();
}Now:
@Role("ADMIN")
@Role("USER")
class Employee {
}The same annotation is applied twice.
Annotations on Parameters
Annotations can also be applied to method parameters.
Example
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface Required {
}Usage:
class UserService {
public void createUser(
@Required String username) {
System.out.println(username);
}
}Parameter annotations are commonly used by frameworks for validation, dependency injection, request mapping, and other purposes.
Annotations on Fields
Example
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Important {
}Usage:
class Employee {
@Important
private String name;
private int age;
}Reflection can inspect the fields and check whether @Important is present.
Annotations on Constructors
Example
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
@interface Inject {
}Usage:
class Service {
@Inject
public Service() {
}
}Frameworks can inspect constructors and use annotations to determine which constructor should be used.
Annotation Processing
Annotations can be processed in different ways.
1. Compiler
The Java compiler can use annotations such as:
@Overrideto validate code.
2. IDE
IDEs can use annotations to provide:
- Warnings
- Code navigation
- Suggestions
- Error highlighting
3. Runtime Reflection
Runtime annotations can be read using reflection.
Example
method.isAnnotationPresent(MyAnnotation.class);4. Annotation Processors
Annotation processors can inspect source code during compilation and generate additional source files or perform checks.
This technique is used by various Java tools and libraries.
Annotations vs Comments
Annotations and comments are different.
Comment
// This method calculates totalA comment is primarily intended for humans and is not normally processed as program metadata.
Annotation
@OverrideAn annotation can be processed by the compiler, tools, frameworks, or runtime reflection depending on its definition.
| Comment | Annotation |
|---|---|
| Mainly for humans | Provides program metadata |
| Not normally processed by Java compiler as metadata | Can be processed by compiler/tools/frameworks |
Uses //, /* */, /** */ | Uses @ |
| Does not affect program metadata processing | Can influence compilation, tools, or framework behavior |
Annotations vs Interfaces
An annotation is not the same as an interface.
Interface
interface Printable {
void print();
}An interface primarily defines a contract that classes can implement.
Annotation
@interface Printable {
}An annotation defines metadata that can be attached to program elements.
Annotations in Frameworks
Annotations are heavily used in modern Java frameworks.
For example, Spring applications commonly use annotations such as:
@Component
@Service
@Repository
@Controller
@AutowiredA class might look like:
@Service
public class UserService {
public void createUser() {
System.out.println("User created");
}
}The framework can inspect the annotation and use it to determine how the class should be managed.
Similarly, testing frameworks use annotations such as:
@Testto identify test methods.
This demonstrates an important concept:
An annotation itself generally provides metadata; some other component interprets that metadata and performs the corresponding behavior.
Commonly Used Java Annotations
| Annotation | Purpose |
|---|---|
@Override | Indicates overriding |
@Deprecated | Marks deprecated API |
@SuppressWarnings | Suppresses compiler warnings |
@FunctionalInterface | Identifies a functional interface |
@SafeVarargs | Suppresses certain varargs warnings |
@Retention | Defines annotation lifetime |
@Target | Defines where annotation can be applied |
@Documented | Includes annotation in generated documentation |
@Inherited | Allows certain class annotations to be inherited |
@Repeatable | Allows repeated annotations |
Important Annotation Interfaces
Java provides annotation-related types in:
java.lang.annotationSome important types are:
Annotation
ElementType
Retention
RetentionPolicy
Target
Documented
Inherited
RepeatableChecking Whether an Annotation Exists
Reflection provides:
isAnnotationPresent()Example
if (Employee.class.isAnnotationPresent(EmployeeInfo.class)) {
System.out.println("Annotation exists");
}Output
Annotation existsGetting All Annotations
We can use:
getAnnotations()Example
Annotation[] annotations =
Employee.class.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}For annotations that are intended to be visible through reflection, their retention policy needs to support runtime availability.
getAnnotation() vs getDeclaredAnnotation()
Two commonly used methods are:
getAnnotation()and:
getDeclaredAnnotation()getAnnotation() can take annotation inheritance into account for applicable class-level annotations.
getDeclaredAnnotation() looks specifically at the annotation declared directly on that element.
Similarly, reflection provides:
getAnnotations()
getDeclaredAnnotations()The Declared versions focus on annotations declared directly on the element.
Practical Example: Custom Validation Annotation
Let's create a simple annotation to mark fields that are required.
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Required {
}Create a class:
class User {
@Required
private String username;
private String email;
User(String username, String email) {
this.username = username;
this.email = email;
}
}Now a validation utility can inspect the fields:
import java.lang.reflect.Field;
class Validator {
static void validate(Object object) throws IllegalAccessException {
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Required.class)) {
field.setAccessible(true);
Object value = field.get(object);
if (value == null ||
value.toString().isBlank()) {
System.out.println(
field.getName() + " is required"
);
}
}
}
}
}Usage:
import java.lang.annotation.*;
import java.lang.reflect.Field;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Required {
}
class Validator {
static void validate(Object object) throws IllegalAccessException {
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Required.class)) {
field.setAccessible(true);
Object value = field.get(object);
if (value == null ||
value.toString().isBlank()) {
System.out.println(
field.getName() + " is required"
);
}
}
}
}
}
class User {
@Required
private String username;
private String email;
User(String username, String email) {
this.username = username;
this.email = email;
}
}
public class HelloWorld {
public static void main(String[] args)
throws IllegalAccessException {
User user = new User("", "john@example.com");
Validator.validate(user);
}
}Output
username is requiredThis is a simplified example of how annotation-driven processing can be designed.
Real validation frameworks provide much more functionality and should generally be preferred over building validation infrastructure from scratch for production applications.
Advantages of Annotations
1. Less Configuration
Annotations can keep configuration close to the code it describes.
2. Better Readability
For example:
@Overrideimmediately communicates the developer's intention.
3. Framework Integration
Annotations are extensively used by Java frameworks.
4. Compile-Time Checking
Some annotations allow the compiler to detect programming mistakes.
5. Runtime Processing
Runtime annotations can be inspected using reflection.
6. Extensibility
Developers can create custom annotations for application-specific requirements.
Disadvantages of Annotations
1. Too Many Annotations Can Reduce Readability
A class containing many annotations can become difficult to understand.
2. Runtime Reflection Can Have Overhead
Applications that heavily rely on runtime reflection can introduce additional complexity and overhead.
3. Hidden Behavior
Frameworks may perform significant operations based on annotations, which can make behavior less obvious to someone unfamiliar with the framework.
4. Incorrect Configuration
An incorrectly configured annotation may cause application startup or runtime problems.
Common Mistakes
Mistake 1: Forgetting Runtime Retention
If you want to read an annotation using reflection:
@Retention(RetentionPolicy.RUNTIME)is generally required.
Mistake 2: Using the Wrong Target
If:
@Target(ElementType.METHOD)is specified, the annotation cannot be used on a class.
Mistake 3: Assuming Every Annotation Has Runtime Behavior
An annotation does not automatically execute code.
For example:
@MyAnnotation
class Demo {
}does not by itself make Java execute anything.
Some compiler, tool, processor, framework, or runtime code must interpret the annotation.
Mistake 4: Using @SuppressWarnings to Hide Real Problems
Avoid blindly writing:
@SuppressWarnings("all")Instead, suppress only the specific warning when you understand why it is safe.
Mistake 5: Confusing @Inherited with General Inheritance
@Inherited applies to certain class-level annotation inheritance behavior. It does not mean annotations on every method and field are automatically inherited.
Key Points
- Annotations provide metadata in Java.
- Annotations start with the
@symbol. - Java provides predefined annotations such as
@Overrideand@Deprecated. - Developers can create custom annotations using
@interface. @Targetdefines where an annotation can be used.@Retentiondefines how long the annotation is retained.SOURCEannotations are discarded after compilation.CLASSannotations are stored in class files.RUNTIMEannotations can be inspected through reflection.@Inheritedcan provide inheritance behavior for applicable class annotations.@Repeatableallows an annotation to be used multiple times.- Runtime annotations are commonly used by frameworks.
- Reflection can be used to inspect runtime annotations.
- Annotation processing can also happen during compilation.
- An annotation does not automatically execute behavior; something must interpret it.
Frequently Asked Questions
What is an annotation in Java?
An annotation is metadata attached to program elements such as classes, methods, fields, constructors, or parameters.
What symbol is used for annotations?
The @ symbol.
Example
@OverrideWhat is a custom annotation?
A custom annotation is an annotation created by the developer using:
@interfaceExample
@interface MyAnnotation {
}What is @Retention?
@Retention specifies how long an annotation should be retained.
The available policies are:
SOURCE
CLASS
RUNTIMEWhich retention policy is required for reflection?
Usually:
RetentionPolicy.RUNTIMEWhat is @Target?
@Target specifies the program elements on which an annotation can be applied.
Can annotations contain values?
Yes.
Example
@interface Employee {
String name();
}Usage:
@Employee(name = "John")Can annotations be inherited?
Some class-level annotations can be inherited when the annotation type is declared with:
@InheritedAre annotations executed?
Annotations themselves do not normally execute business logic. They provide metadata that can be interpreted by the compiler, tools, annotation processors, frameworks, or runtime code.
Conclusion
Java annotations provide a powerful way to attach metadata to program elements. They are used by the Java compiler, development tools, annotation processors, and frameworks to understand how different parts of an application should be treated.
The most important concepts to remember are:
Annotation
↓
Metadata
↓
@Target → Where it can be used
@Retention → How long it is available
@Inherited → Applicable class-level inheritance behavior
@Repeatable → Allows repeated annotations
↓
Processing
↓
Compiler / Tools / Annotation Processor / Reflection / FrameworkUnderstanding annotations is especially important when working with modern Java frameworks because many frameworks use annotations as a primary mechanism for configuration and behavior.
See you in next page....🙂