@Overridepublic String toString() {
return "Employee";
}Java Annotations Overview: Metadata, Built-In Annotations, and Retention
Direct Answer & Core Concept
Java annotations provide a way to attach metadata to classes, methods, fields, parameters, constructors, and other program elements. They begin with the @ symbol and can give useful information to the compiler, development tools, frameworks, or runtime code.
You have probably already seen annotations such as @Override, @Deprecated, and @SuppressWarnings. These annotations do not normally represent executable business logic. Instead, they describe how another tool or part of the Java platform should treat a program element.
Understanding annotations is important because modern Java frameworks use them extensively for configuration, dependency injection, validation, testing, serialization, web development, and many other tasks.
Detailed Explanation & Mechanics
What Is an Annotation in Java?
An annotation is metadata attached to a program element.
For example:
Here:
@Overrideis an annotation applied to the toString() method.
The annotation tells the compiler that the method is intended to override a method from a superclass.
Why Are Annotations Used?
Annotations can provide information for different purposes.
They can be used by:
The Java compiler
Development tools
Build tools
Annotation processors
Testing frameworks
Runtime frameworks
Libraries
Application code
For example:
@Deprecatedpublic void oldMethod() {
}The annotation communicates that the method is deprecated.
An IDE can then display a warning when another part of the program uses it.
Annotation Syntax
The basic syntax starts with @ followed by the annotation name.
@OverrideAn annotation can also contain values:
@SuppressWarnings("unchecked")Multiple values can be specified:
@SomeAnnotation( name = "Employee", version = 1)The exact values allowed depend on how the annotation type is defined.
Annotations Are Metadata
It is useful to think of annotations as information attached to code.
For example:
Class | +-- Annotation | +-- Fields | +-- Methods | +-- ConstructorsThe annotation itself does not automatically execute application logic.
Another component may inspect or process the annotation and take some action based on its presence or values.
Common Built-In Java Annotations
Java provides several important built-in annotations.
Some commonly encountered annotations include:
@Override@Deprecated@SuppressWarnings@SafeVarargs@FunctionalInterface
There are also meta-annotations used to define how annotations behave, such as:
@Retention@Target@Documented@Inherited@Repeatable
The @Override Annotation
@Override indicates that a method is intended to override a method declared by a superclass or interface.
Example:
class Animal {
void sound() {
System.out.println("Animal sound");
}
}class Dog extends Animal {
@Override void sound() {
System.out.println("Dog barks");
}
}The annotation helps the compiler detect mistakes.
For example, suppose you accidentally change the method name:
class Dog extends Animal {
@Override void sounds() {
System.out.println("Dog barks");
}
}The compiler can report an error because sounds() does not override the expected method.
Without @Override, the mistake could instead result in an accidental overload or unrelated method.
Why @Override Is Useful
Consider:
@Overridepublic String toString() {
return "Employee";
}The annotation communicates the programmer's intention:
"This method should override an inherited method."It therefore provides both documentation and compile-time checking.
A good practice is to use @Override whenever a method is intentionally overriding another method.
The @Deprecated Annotation
@Deprecated indicates that a program element should generally no longer be used for new code.
Example:
@Deprecatedpublic void oldMethod() {
System.out.println("Old implementation");
}When another part of the program calls it:
oldMethod();the compiler or IDE can produce a deprecation warning.
@Deprecated With since and forRemoval
Modern Java also supports additional information with @Deprecated.
For example:
@Deprecated( since = "2.0", forRemoval = true)public void oldMethod() {
}This communicates that the API was deprecated starting with a particular version and may be intended for removal.
The annotation itself does not automatically delete the method.
It provides metadata that tools and developers can use.
The @SuppressWarnings Annotation
@SuppressWarnings tells the compiler that certain warnings should be suppressed for the annotated program element.
For example:
@SuppressWarnings("unchecked")public void process() {
// code that produces an unchecked warning
}It can also be applied to a local variable, method, class, or other supported target.
Another example:
@SuppressWarnings( {
"unchecked", "rawtypes"
})Multiple warning categories can be specified.
Use @SuppressWarnings Carefully
Suppressing warnings does not fix the underlying problem.
For example:
@SuppressWarnings("unchecked")does not make an unsafe operation safe.
It simply tells the compiler not to report that particular warning in the applicable scope.
A good practice is to keep the suppression as narrow as reasonably possible.
The @SafeVarargs Annotation
@SafeVarargs can be used on certain methods or constructors involving variable-arity parameters when the implementation is known to be safe with respect to heap pollution concerns.
For example:
@SafeVarargsstatic <T> void printItems(T... items) {
for (T item : items) {
System.out.println(item);
}
}It is intended for cases where the programmer can guarantee that the variable-arity operation does not perform unsafe operations that would violate the intended type safety.
This annotation should not be used simply to hide warnings without understanding why the operation is safe.
The @FunctionalInterface Annotation
@FunctionalInterface indicates that an interface is intended to be a functional interface.
A functional interface has one abstract method, although it can also contain methods that do not count as abstract methods, such as appropriate default or static methods.
Example:
@FunctionalInterfaceinterface Calculator {
int calculate(int a, int b);
}The annotation allows the compiler to verify the intended structure.
For example, this would violate the functional-interface requirement:
@FunctionalInterfaceinterface Calculator {
int calculate(int a, int b);
int multiply(int a, int b);
}The compiler can report an error because the interface has more than one abstract method.
Annotation Values
Annotations can contain elements, which are similar to parameters.
For example:
@SuppressWarnings("unchecked")The value is supplied to the annotation's value element.
Some annotations use named elements:
@Example( name = "Java", version = 1)Annotation element values must be compatible with the types allowed by the annotation declaration.
Default Annotation Values
An annotation element can define a default value.
Example:
@interface Info {
String author() default "Unknown";
int version() default 1;
}Now the annotation can be used without specifying every element:
@Infoclass Application {
}The defaults are used for omitted elements.
You can also override them:
@Info( author = "John", version = 2)class Application {
}What Is a Custom Annotation?
Java allows developers to define their own annotation types.
The basic syntax uses @interface.
Example:
@interface Author {
String name();
}Now it can be applied like this:
@Author(name = "John")class Book {
}The custom annotation describes metadata associated with the Book class.
Custom Annotation Example
Let's create a simple annotation for identifying application components.
@interface Component {
String name();
}Use it:
@Component(name = "PaymentService")class PaymentService {
}The annotation itself does not automatically create or register the service.
Some other code, framework, or annotation processor would need to inspect the annotation and implement that behavior.
This distinction is important:
Defining an annotation does not automatically give it behavior.
Annotation Interfaces
An annotation declaration looks unusual because it uses:
@interfacerather than:
interfaceFor example:
@interface Author {
String name();
}The result is an annotation type.
Annotations can define elements, but those elements have restrictions on their allowed types.
Common annotation element types include:
primitive types
StringClassenum types
annotation types
arrays of these types
Using Class in an Annotation
An annotation can contain a Class value.
Example:
@interface Handler {
Class<?> type();
}Usage:
@Handler(type = String.class)class TextHandler {
}This can be useful when metadata needs to refer to a particular Java type.
Annotation Arrays
Annotation elements can also be arrays.
Example:
@interface Roles {
String[] value();
}Usage:
@Roles( {
"ADMIN", "USER"
})class Account {
}You can also explicitly write:
@Roles(value = {
"ADMIN", "USER"
})class Account {
}The @Retention Annotation
One of the most important annotation concepts is retention.
@Retention determines how long an annotation should be retained.
The three main retention policies are:
SOURCECLASSRUNTIMEThese determine whether annotation information is available at different stages.
RetentionPolicy.SOURCE
SOURCE means the annotation is available in source code but is not retained in the compiled class file.
Example:
@Retention(RetentionPolicy.SOURCE)@interface MyAnnotation {
}The annotation can be used while compiling source code, but it is not stored in the resulting .class file.
This can be useful for annotations processed only during compilation.
RetentionPolicy.CLASS
CLASS means the annotation is recorded in the class file but is not necessarily available through normal runtime reflection.
Example:
@Retention(RetentionPolicy.CLASS)@interface MyAnnotation {
}CLASS is the default retention policy when no @Retention annotation is specified.
This makes the annotation information available in compiled class files for tools that inspect them, while ordinary runtime reflection does not expose it as a runtime annotation.
RetentionPolicy.RUNTIME
RUNTIME means the annotation is retained in the class file and available at runtime through reflection.
Example:
@Retention(RetentionPolicy.RUNTIME)@interface MyAnnotation {
String value();
}Now code can inspect it at runtime.
For example:
@MyAnnotation("Java")class Example {
}Reflection can access the annotation:
MyAnnotation annotation = Example.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value());This is one reason runtime annotations are heavily used by frameworks.
Retention Policy Comparison
| Retention | Source Code | Class File | Runtime Reflection |
|---|---|---|---|
SOURCE | Yes | No | No |
CLASS | Yes | Yes | Not normally available |
RUNTIME | Yes | Yes | Yes |
A simple mental model is:
SOURCE↓Source onlyCLASS↓Source + .class fileRUNTIME↓Source + .class file + runtime reflectionThe @Target Annotation
@Target specifies where an annotation can be used.
For example:
@Target(ElementType.METHOD)@interface Loggable {
}This annotation can be applied to methods.
@Loggablepublic void save() {
}But it cannot be freely applied to every kind of program element.
Common Element Types
ElementType includes targets such as:
TYPEFIELDMETHODPARAMETERCONSTRUCTORLOCAL_VARIABLEPACKAGEMODULETYPE_PARAMETERTYPE_USE
For example:
@Target(ElementType.TYPE)@interface Entity {
}This indicates that the annotation is intended for types such as classes or interfaces.
Combining @Target and @Retention
A custom annotation often specifies both.
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@interface Loggable {
}Now the annotation:
can be applied to methods
remains available at runtime
Example:
class UserService {
@Loggable public void createUser() {
System.out.println("Creating user");
}
}A framework can inspect the method and discover the annotation at runtime.
Reading Runtime Annotations With Reflection
Consider:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@interface Component {
String value();
}Apply it:
@Component("PaymentService")class PaymentService {
}Now inspect it:
public class Main {
public static void main(String[] args) {
Component annotation = PaymentService.class .getAnnotation(Component.class);
if (annotation != null) {
System.out.println( annotation.value());
}
}
}Possible output:
PaymentServiceThe important part is that the annotation uses:
RetentionPolicy.RUNTIMEWithout runtime retention, ordinary reflection cannot retrieve it as a runtime annotation.
The @Documented Annotation
@Documented indicates that an annotation should be included in generated API documentation when the documentation tool supports it.
Example:
@Documented@Retention(RetentionPolicy.RUNTIME)@interface ApiInfo {
String value();
}It is mainly useful for annotations intended to be part of a public API's documentation.
The @Inherited Annotation
@Inherited affects annotation inheritance for class annotations.
Example:
@Inherited@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@interface Role {
String value();
}Apply it to a superclass:
@Role("ADMIN")class Parent {
}A subclass can inherit the annotation when queried through the class-level reflection mechanism that supports @Inherited:
class Child extends Parent {
}However, @Inherited does not mean that annotations are automatically inherited in every context.
In particular, it applies to class annotations and does not provide general inheritance behavior for methods or fields.
The @Repeatable Annotation
@Repeatable allows the same annotation type to be applied multiple times to the same declaration.
For example:
@Repeatable(Roles.class)@interface Role {
String value();
}Define the container annotation:
@interface Roles {
Role[] value();
}Now you can write:
@Role("ADMIN")@Role("USER")class Account {
}This is useful when a program element needs multiple metadata entries of the same annotation type.
Meta-Annotations
Annotations that describe other annotations are called meta-annotations.
Important examples include:
@Retention@Target@Documented@Inherited@RepeatableFor example:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@interface TestMethod {
}Here:
@Retentiondescribes how long@TestMethodis retained.@Targetdescribes where@TestMethodcan be used.
Annotations and Compilation
Annotations can be useful during compilation.
Some tools and annotation processors inspect annotations while source code is being compiled.
For example, an annotation could mark a class:
@GenerateCodeclass User {
}An annotation processor could detect the annotation and generate additional source code or other compiler-time artifacts.
This is one reason annotations are important in modern Java development.
Annotations and Runtime Reflection
Runtime annotations can also be inspected after the application starts.
For example:
@Retention(RetentionPolicy.RUNTIME)@interface Version {
int value();
}Use:
@Version(2)class Application {
}Read:
Version version = Application.class .getAnnotation(Version.class);
System.out.println(version.value());Output:
2Frameworks can use this technique to discover configuration metadata dynamically.
Annotations Do Not Automatically Execute Code
Consider:
@MyAnnotationclass Example {
}The annotation does not automatically call a method.
There must be something that interprets it.
For example:
Annotation↓Compiler / Processor / Framework / Reflection↓ActionThis is a critical concept for beginners.
Annotations provide metadata; another component gives that metadata meaning.
Annotations in Frameworks
Annotations are widely used by Java frameworks.
For example, frameworks can use annotations to describe:
components
configuration
routes
database entities
validation rules
dependency injection
test methods
serialization rules
The framework reads the metadata and performs the appropriate operation.
This is why understanding annotations is useful even if you do not create custom annotations yourself.
Common Built-In Annotation Summary
| Annotation | Main Purpose |
|---|---|
@Override | Indicates an overriding method |
@Deprecated | Marks an API as deprecated |
@SuppressWarnings | Suppresses selected compiler warnings |
@SafeVarargs | Indicates safe handling of certain varargs operations |
@FunctionalInterface | Indicates an intended functional interface |
@Retention | Defines annotation retention |
@Target | Defines valid annotation targets |
@Documented | Includes annotation information in generated documentation |
@Inherited | Enables inheritance of certain class annotations |
@Repeatable | Allows repeated use of an annotation |
Common Mistakes With Annotations
Mistake 1: Thinking Every Annotation Runs Code
Annotations are metadata.
They require a compiler, processor, framework, or runtime code to interpret them.
Mistake 2: Choosing the Wrong Retention
If an annotation needs runtime reflection, use:
@Retention(RetentionPolicy.RUNTIME)A SOURCE annotation will not be available at runtime.
Mistake 3: Forgetting @Target
If an annotation should only apply to methods, make that restriction explicit:
@Target(ElementType.METHOD)This helps catch incorrect usage.
Mistake 4: Suppressing Warnings Without Understanding Them
Avoid using:
@SuppressWarnings("unchecked")simply to make warnings disappear.
Understand the warning first and keep suppression as narrow as practical.
Mistake 5: Assuming @Inherited Applies Everywhere
@Inherited applies to class-level annotation inheritance through the relevant reflection behavior. It does not automatically make method or field annotations inherit through subclasses.
Best Practices for Java Annotations
Use standard annotations when they already solve the problem
For example:
@Overrideis preferable to creating your own annotation for the same purpose.
Keep custom annotations focused
An annotation should communicate one clear piece of metadata or configuration.
Choose retention deliberately
Ask:
Does this annotation need to exist only in source?Does a tool need it in the class file?Does runtime code need to inspect it?Then select the appropriate retention policy.
Restrict annotation targets
Use @Target when the annotation has specific valid locations.
Document public annotations
If an annotation is part of a public API, @Documented may be appropriate.
Frequently Asked Questions
What is an annotation in Java?
An annotation is metadata attached to a Java program element. It can provide information to the compiler, tools, annotation processors, frameworks, or runtime code.
What does @Override do?
It tells the compiler that a method is intended to override an inherited method and allows the compiler to detect certain mistakes.
What is @Deprecated used for?
It marks an API element as deprecated so developers and tools can be warned that it should generally no longer be used for new code.
What does @SuppressWarnings do?
It suppresses specified compiler warnings within its applicable scope. It does not fix the underlying issue.
What are annotation retention policies?
The main policies are SOURCE, CLASS, and RUNTIME. They determine how long annotation information is retained.
Which retention policy allows reflection?
RetentionPolicy.RUNTIME allows an annotation to be available through runtime reflection.
What is @Target?
@Target specifies the kinds of program elements where an annotation can be used.
What is a custom annotation?
A custom annotation is an annotation type created by the developer using syntax such as:
@interface MyAnnotation {
}What is a meta-annotation?
A meta-annotation is an annotation used to describe another annotation type, such as @Target or @Retention.
Do annotations execute code?
Not by themselves. A compiler, annotation processor, framework, tool, or application code must interpret the annotation and perform some action.
What is @FunctionalInterface?
It indicates that an interface is intended to have one abstract method and allows the compiler to verify that intention.
What is @Repeatable?
It allows an annotation type to be applied multiple times to the same declaration.
Suggested Graphic
Create a visual diagram showing how an annotation moves through the Java development lifecycle:
JAVA ANNOTATION | +--------+--------+ | | Built-in Custom | | @Override @MyAnnotation @Deprecated | @FunctionalInterface | +--------+--------+ | @Retention | +--------------+--------------+ | | | SOURCE CLASS RUNTIME | | | Source .class file Reflection | FrameworkThe graphic should emphasize the three retention levels and show that annotations provide metadata that other tools or runtime code can interpret.
Runnable Code Example: Java Annotations Overview
Java 17+public class AnnotationsOverviewDemo {
@Deprecated(since = "2.0", forRemoval = true) public static void legacyMethod() {
System.out.println("Deprecated method called");
} @SuppressWarnings("deprecation") public static void main(String[] args) {
legacyMethod();
System.out.println("Annotations provide powerful compile-time and runtime metadata!");
}
}