last modified July 4, 2024
In this article we talk about Java annotations.
Annotations are a special kind of code element that provides additional information about a program. They are attached to classes, methods, fields, and other program elements using the @ symbol.
Annotations themselves don’t directly affect how the code runs, but instead provide metadata that can be used by various tools:
Compiler: The compiler can use annotations to check for errors or suppress
warnings during compilation. For instance, the @Override annotation tells
the compiler to verify that a method is actually overriding a method in a
superclass.
Processing tools: Annotations can be processed by various tools during
compilation or deployment time. These tools can use the information in the
annotations to generate code, configuration files, or perform other tasks. For
example, a framework might use annotations to configure how a class is mapped
to a database table.
Runtime: Some annotations are available for introspection at runtime using
reflection. This allows programs to examine the annotations on classes and
methods and act accordingly.
Here are some key points about Java annotations:
They are metadata: Annotations provide supplemental information about the
program, not the core functionality.
Custom vs. predefined: You can define your own annotations or use
predefined annotations that are part of the Java platform or third-party
libraries.
Scope: Annotations can be applied to various program elements like
classes, methods, fields, etc.
Retention: Annotations can have different lifespans. Some are retained
only during compilation, while others are available at runtime via reflection.
The @Override annotation in Java is specifically used for method overriding in inheritance. It’s a marker annotation to improve code clarity and catch errors during compilation.
Here’s what @Override does:
Indicates intent: By placing @Override before a method
declaration in a subclass, you explicitly tell the compiler that you
intend to override a method from the superclass.
Compiler checks: The compiler verifies if the method with the
@Override annotation actually overrides a method with the
same name, parameter list, and return type in the direct superclass. If
not, it throws a compilation error. This helps prevent mistakes like
typos in method names or accidental overloads (methods with the same
name but different parameter lists).
Improved readability: Using @Override makes your code
clearer by highlighting methods that are intended to override
functionalities from the parent class.
@Override is not mandatory for overriding methods. The compiler can usually figure out if a method is intended to override based on the signature. However, it’s considered good practice to use @Override for better code clarity and catching potential errors early on.
@Override is not used for implementing methods in interfaces. Interfaces only declare methods, and subclasses implementing interfaces must provide their own implementation. The compiler inherently checks for this relationship.
Main.java
class User {
private final String firstName;
private final String lastName;
private final String occupation;
public User(String firstName, String lastName, String occupation) {
this.firstName = firstName;
this.lastName = lastName;
this.occupation = occupation;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getOccupation() {
return occupation;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("User{");
sb.append("firstName='").append(firstName).append('\'');
sb.append(", lastName='").append(lastName).append('\'');
sb.append(", occupation='").append(occupation).append('\'');
sb.append('}');
return sb.toString();
}
}
void main() {
User[] users = {
new User("John", "Doe", "gardener"),
new User("Roger", "Roe", "driver"),
new User("Paul", "Smith", "teacher"),
};
for (User user : users) {
System.out.println(user);
}
}
In the example, we override the Object’s toString method. The method is annotated with @Override. If the function does not match the parent’s one (for instance we use private instead of public) we get a warning: Cannot reduce the visibility of the inherited method from Object.
The @Deprecated annotation in Java is used to mark classes, methods, fields, or constructors that are no longer recommended for use. It serves as a warning to developers that they should avoid using these elements and consider alternatives.
Key features of the @Deprecated annotation:
Signaling intent: By annotating a program element with
@Deprecated, you indicate that it's discouraged from being
used in new code. There might be better alternatives available or the
element might be scheduled for removal in future versions.
Compiler warnings: When the compiler encounters a code element marked as
@Deprecated, it generates a warning message. This helps
developers identify potential issues and encourages them to migrate to
the suggested alternatives.
Refactoring: The @Deprecated annotation is often used
during the evolution of APIs. As libraries and frameworks mature, some
functionalities might become outdated or replaced by more efficient
approaches. Deprecation allows for a gradual transition without breaking
existing code that relies on the old elements.
Additional features of the @Deprecated annotation (Java 9 onwards):
since (optional): Specifies the version in which the element was
deprecated. This provides context for developers about the history of
the deprecation.
forRemoval (optional): Indicates that the element is planned for removal
in a future major release. This stronger warning encourages developers
to find alternatives as soon as possible.
Main.java
class PassWordGenerator {
@Deprecated
public String generatePassword() {
return "generated password";
}
public String generateSecurePassword() {
return "a secure password";
}
}
// @SuppressWarnings(“deprecation”) void main() {
var pgen = new PassWordGenerator();
System.out.println(pgen.generateSecurePassword());
System.out.println(pgen.generatePassword());
}
In the example, we mark the generatePassword with the @Deprecated annotation, because it is being replaced with a more secure alternative.
The @Deprecated annotation can be suppressed with @SuppressWarnings(“deprecation”) annotation.
We can create our own annotations in Java. Key elements of a custom Java annotation are summarized in the following table:
Element
Description
@interface
Declaration for creating a custom annotation.
@Target
Specifies where the annotation can be applied (fields, classes, methods, etc.).
@Retention
Determines how long the annotation information is retained (compile-time or runtime).
Elements (methods)
Define attributes or parameters for the annotation (with data types and default values).
Doc comments
Optional comments to document the annotation and its usage.
The next example creates a custom annotation.
com/zetcode/ClassDescription.java
package com.zetcode;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ClassDescription { String description(); }
We have a ClassDescription that is targeted on classes.
com/ztetcode/AnnotationProcessor.java
package com.zetcode;
import java.util.List; import java.util.Optional;
public class AnnotationProcessor {
public void process(List<Object> objects) {
objects.forEach(obj -> {
Class<?> clazz = obj.getClass();
ClassDescription annotation = clazz.getAnnotation(ClassDescription.class);
// if (annotation != null) { // System.out.println(annotation.annotationType().getName()); // System.out.println(annotation.description()); // }
Optional.ofNullable(annotation)
.ifPresentOrElse(
ann -> {
System.out.println(ann.annotationType().getName());
System.out.println(ann.description());
},
() -> System.out.println("No annotation found")
);
});
}
}
The AnnotationProcessor will process the annotation.
com/zetcode/CustomAnnotation.java
package com.zetcode;
import java.util.List;
@ClassDescription(description = “this is a User class”) class User {
}
@ClassDescription(description = “this is a Test class”) class Test {
}
class Hello {
}
public class CustomAnnotation {
private String field;
public static void main(String[] args) {
List<Object> objects = List.of(new User(), new Test(), new Hello());
var processor = new AnnotationProcessor();
processor.process(objects);
}
}
We have two classes that are decorated with @ClassDescription. We process the instance objects with AnnotationProcessor that will print the description of the decorated class.
In this article we covered annotations in Java. We have shown how to use existing annotations and how to create custom one.
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.
List all Java tutorials.