Java Annotations
Annotations are a form of metadata that provide data about a program but are not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.
They are widely used by compiler tools (to detect errors or suppress warnings), build-time tools (to generate code or XML configuration files), and runtime frameworks (such as Spring, Hibernate, or Jackson) to dynamically configure and customize program behavior.
Meta-Annotations
Meta-annotations are annotations that are applied to other annotations. They define the behavior and scope of your custom annotations.
1. @Retention
Defines how long the annotation should be kept:
RetentionPolicy.SOURCE: Discarded by the compiler; not present in the compiled.classfile (e.g.@Override).RetentionPolicy.CLASS: Recorded in the.classfile but discarded by the JVM at runtime (this is the default).RetentionPolicy.RUNTIME: Retained in the.classfile and loaded by the JVM at runtime. Must be set toRUNTIMEif you want to read the annotation via reflection.
2. @Target
Defines where the annotation can be applied. Common ElementType targets include:
ElementType.TYPE: Class, interface, or enumElementType.FIELD: Fields or variablesElementType.METHOD: MethodsElementType.PARAMETER: Method parametersElementType.CONSTRUCTOR: Constructors
Creating a Custom Annotation
Custom annotations are defined using the @interface keyword. The methods in the interface define the properties of the annotation.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD) // Can only be used on fields
@Retention(RetentionPolicy.RUNTIME) // Can be inspected at runtime
public @interface JsonField {
String value() default ""; // An element with a default value
}
Practical Code Example: Custom JSON Serializer
Here is a complete example of creating a custom annotation @JsonField and writing a processing class that uses reflection to serialize any object to a JSON string using those annotations.
Step 1: The Annotation and Model
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// Define custom annotation
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface JsonField {
String key() default ""; // Maps the field to a custom JSON key
}
// Class using custom annotations
public class Book {
@JsonField(key = "book_title")
private String title;
@JsonField(key = "author_name")
private String author;
// This field has no annotation and will be ignored by our serializer
private double price;
public Book(String title, String author, double price) {
this.title = title;
this.author = author;
this.price = price;
}
}
Step 2: The Annotation Processor (Serializer)
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class JsonSerializer {
public static String serialize(Object obj) {
Class<?> clazz = obj.getClass();
Map<String, String> jsonMap = new HashMap<>();
// Loop through all fields of the class
for (Field field : clazz.getDeclaredFields()) {
// Check if our custom annotation is present
if (field.isAnnotationPresent(JsonField.class)) {
field.setAccessible(true); // Allow accessing private fields
try {
// Get the annotation instance
JsonField annotation = field.getAnnotation(JsonField.class);
// Determine the JSON key (use the custom key or fall back to field name)
String key = annotation.key().isEmpty() ? field.getName() : annotation.key();
Object value = field.get(obj);
jsonMap.put(key, value == null ? "null" : "\"" + value.toString() + "\"");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
// Format map as a JSON string: {"key1": "val1", "key2": "val2"}
String jsonString = jsonMap.entrySet().stream()
.map(entry -> entry.getKey() + ":" + entry.getValue())
.collect(Collectors.joining(","));
return "{" + jsonString + "}";
}
public static void main(String[] args) {
Book book = new Book("Effective Java", "Joshua Bloch", 45.00);
// Serialize the Book object to JSON
String json = JsonSerializer.serialize(book);
System.out.println("Serialized JSON: " + json);
// Output: {book_title:"Effective Java",author_name:"Joshua Bloch"}
}
}
[!TIP] Notice how the
pricefield was omitted from the JSON output because it didn't have the@JsonFieldannotation. This is exactly how frameworks like Jackson, Gson, or Spring serialize and map resources under the hood!