Table of Contents
- Introduction to Annotations
- Built-in Annotations
- Custom Annotations
- Annotation Retention
- Annotation Targets
- Annotation Parameters
- Repeatable Annotations
- Annotation Processing
- Best Practices
- Complete Examples
Introduction to Annotations
Annotations are metadata that provide data about a program but are not part of the program itself. They have no direct effect on the operation of the code they annotate.
Basic Syntax:
@AnnotationName
public class MyClass {
@AnnotationName
private String field;
@AnnotationName
public void method() {
// method body
}
}
Simple Example:
// Using built-in annotation
@Deprecated
public class OldClass {
@SuppressWarnings("unchecked")
public void oldMethod() {
List list = new ArrayList(); // Raw type - generates warning
list.add("test");
}
}
public class AnnotationDemo {
public static void main(String[] args) {
OldClass old = new OldClass();
old.oldMethod();
}
}
Built-in Annotations
1. @Override:
public class OverrideExample {
static class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
public void eat() {
System.out.println("Animal is eating");
}
}
static class Dog extends Animal {
// ✅ Correct - overrides parent method
@Override
public void makeSound() {
System.out.println("Dog barks");
}
// ❌ Compile-time error - method doesn't exist in parent
// @Override
// public void makeNoise() { // This would cause error
// System.out.println("Dog makes noise");
// }
// No @Override - but if parent method changes, no error
public void eat() {
System.out.println("Dog is eating");
}
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.makeSound(); // Dog barks
dog.eat(); // Dog is eating
}
}
2. @Deprecated:
public class DeprecatedExample {
/**
* Old way of greeting - use {@link #newGreet(String)} instead
* @deprecated This method is deprecated because it uses old formatting
*/
@Deprecated(since = "2.0", forRemoval = true)
public static void oldGreet(String name) {
System.out.println("Hello, " + name + "!");
}
/**
* New way of greeting with better formatting
*/
public static void newGreet(String name) {
System.out.println("🌟 Hello, " + name + "! 🌟");
}
public static void main(String[] args) {
// This will show warning in IDE
oldGreet("Alice"); // Deprecated method call
// Recommended way
newGreet("Bob");
// Using reflection to check annotation
try {
Method oldMethod = DeprecatedExample.class.getMethod("oldGreet", String.class);
Deprecated deprecated = oldMethod.getAnnotation(Deprecated.class);
if (deprecated != null) {
System.out.println("oldGreet is deprecated since: " + deprecated.since());
System.out.println("Scheduled for removal: " + deprecated.forRemoval());
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
3. @SuppressWarnings:
import java.util.*;
public class SuppressWarningsExample {
// Suppress unchecked warnings for this entire class
@SuppressWarnings("unchecked")
public void processRawList() {
List rawList = new ArrayList(); // Raw type usage
rawList.add("String");
rawList.add(123); // Mixing types
String str = (String) rawList.get(0); // Unchecked cast
}
public void methodWithMultipleWarnings() {
// Suppress multiple warnings
@SuppressWarnings({"rawtypes", "unchecked"})
List list = new ArrayList();
list.add("test");
// Suppress deprecation warning
@SuppressWarnings("deprecation")
Date date = new Date(2020, 1, 1); // Deprecated constructor
}
// Common warning types:
// - "unchecked" - for generic type safety
// - "deprecation" - for use of deprecated items
// - "rawtypes" - for use of raw types
// - "unused" - for unused variables
// - "all" - for all warnings
public static void main(String[] args) {
SuppressWarningsExample example = new SuppressWarningsExample();
example.processRawList();
example.methodWithMultipleWarnings();
}
}
4. @FunctionalInterface:
public class FunctionalInterfaceExample {
// Valid functional interface - has exactly one abstract method
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
// Can have default methods
default void printResult(int result) {
System.out.println("Result: " + result);
}
// Can have static methods
static String getOperationName() {
return "Calculation";
}
}
// ❌ Invalid - will cause compile error
// @FunctionalInterface
// interface InvalidFunctional {
// void method1();
// void method2(); // More than one abstract method
// }
public static void main(String[] args) {
// Using lambda with functional interface
Calculator adder = (a, b) -> a + b;
Calculator multiplier = (a, b) -> a * b;
System.out.println("Addition: " + adder.calculate(5, 3));
System.out.println("Multiplication: " + multiplier.calculate(5, 3));
adder.printResult(adder.calculate(10, 20));
System.out.println("Operation: " + Calculator.getOperationName());
}
}
Custom Annotations
Creating Custom Annotations:
import java.lang.annotation.*;
import java.lang.reflect.*;
// Marker annotation (no parameters)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Development {
// No elements - marker annotation
}
// Single-value annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@interface Author {
String value(); // Special case - can use without name when only one element
}
// Multi-value annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface TestCase {
int id();
String description();
String[] tags() default {}; // Optional element with default value
boolean enabled() default true;
}
// Using the custom annotations
@Development
@Author("John Doe")
class PaymentProcessor {
@TestCase(
id = 1,
description = "Test successful payment",
tags = {"payment", "success"},
enabled = true
)
public void processPayment(double amount) {
System.out.println("Processing payment: $" + amount);
}
@TestCase(
id = 2,
description = "Test failed payment",
tags = {"payment", "failure"},
enabled = false // This test is disabled
)
public void processFailedPayment(double amount) {
System.out.println("Processing failed payment: $" + amount);
}
@Author("Jane Smith")
public void refundPayment(double amount) {
System.out.println("Refunding payment: $" + amount);
}
}
public class CustomAnnotationDemo {
public static void main(String[] args) {
// Process annotations using reflection
Class<PaymentProcessor> processorClass = PaymentProcessor.class;
// Check class-level annotations
if (processorClass.isAnnotationPresent(Development.class)) {
System.out.println("PaymentProcessor is in development");
}
Author classAuthor = processorClass.getAnnotation(Author.class);
if (classAuthor != null) {
System.out.println("Class author: " + classAuthor.value());
}
// Process method annotations
for (Method method : processorClass.getDeclaredMethods()) {
System.out.println("\nMethod: " + method.getName());
// Check TestCase annotation
TestCase testCase = method.getAnnotation(TestCase.class);
if (testCase != null) {
System.out.println(" Test ID: " + testCase.id());
System.out.println(" Description: " + testCase.description());
System.out.println(" Enabled: " + testCase.enabled());
System.out.println(" Tags: " + String.join(", ", testCase.tags()));
}
// Check Author annotation
Author author = method.getAnnotation(Author.class);
if (author != null) {
System.out.println(" Author: " + author.value());
}
}
}
}
Output:
PaymentProcessor is in development Class author: John Doe Method: processPayment Test ID: 1 Description: Test successful payment Enabled: true Tags: payment, success Method: processFailedPayment Test ID: 2 Description: Test failed payment Enabled: false Tags: payment, failure Method: refundPayment Author: Jane Smith
Annotation Retention
Retention Policies:
import java.lang.annotation.*;
// SOURCE - discarded by compiler, not available at runtime
@Retention(RetentionPolicy.SOURCE)
@interface SourceLevelAnnotation {
String value();
}
// CLASS - stored in .class file but not available at runtime (default)
@Retention(RetentionPolicy.CLASS)
@interface ClassLevelAnnotation {
String value();
}
// RUNTIME - stored in .class file and available at runtime
@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeLevelAnnotation {
String value();
}
@SourceLevelAnnotation("This won't be available at runtime")
@ClassLevelAnnotation("This might be available depending on JVM")
@RuntimeLevelAnnotation("This will be available at runtime")
class RetentionDemo {
@SourceLevelAnnotation("source method")
@ClassLevelAnnotation("class method")
@RuntimeLevelAnnotation("runtime method")
public void demoMethod() {
System.out.println("Method demonstration");
}
}
public class RetentionExample {
public static void main(String[] args) {
Class<RetentionDemo> demoClass = RetentionDemo.class;
// Check runtime annotations
System.out.println("=== Runtime Annotations ===");
RuntimeLevelAnnotation runtime = demoClass.getAnnotation(RuntimeLevelAnnotation.class);
if (runtime != null) {
System.out.println("Class Runtime: " + runtime.value());
}
// Check class-level annotations (may not be available)
System.out.println("\n=== Class-level Annotations ===");
ClassLevelAnnotation classLevel = demoClass.getAnnotation(ClassLevelAnnotation.class);
if (classLevel != null) {
System.out.println("Class Class-level: " + classLevel.value());
} else {
System.out.println("Class-level annotation not available at runtime");
}
// Check source-level annotations (won't be available)
System.out.println("\n=== Source-level Annotations ===");
SourceLevelAnnotation source = demoClass.getAnnotation(SourceLevelAnnotation.class);
if (source != null) {
System.out.println("Class Source: " + source.value());
} else {
System.out.println("Source-level annotation not available at runtime");
}
// Check method annotations
try {
Method method = demoClass.getMethod("demoMethod");
System.out.println("\n=== Method Annotations ===");
RuntimeLevelAnnotation methodRuntime = method.getAnnotation(RuntimeLevelAnnotation.class);
if (methodRuntime != null) {
System.out.println("Method Runtime: " + methodRuntime.value());
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
Output:
=== Runtime Annotations === Class Runtime: This will be available at runtime === Class-level Annotations === Class-level annotation not available at runtime === Source-level Annotations === Source-level annotation not available at runtime === Method Annotations === Method Runtime: runtime method
Annotation Targets
Target Elements:
import java.lang.annotation.*;
// Annotation that can be applied to different elements
@Target({
ElementType.TYPE, // Class, interface, enum
ElementType.FIELD, // Field (including enum constants)
ElementType.METHOD, // Method
ElementType.PARAMETER, // Method parameter
ElementType.CONSTRUCTOR, // Constructor
ElementType.LOCAL_VARIABLE, // Local variable
ElementType.ANNOTATION_TYPE,// Annotation type
ElementType.PACKAGE, // Package
ElementType.TYPE_PARAMETER, // Type parameter (Java 8+)
ElementType.TYPE_USE // Type use (Java 8+)
})
@Retention(RetentionPolicy.RUNTIME)
@interface MultiTarget {
String value();
}
// Specific target examples
@Target(ElementType.TYPE)
@interface ClassInfo {
String author();
String version();
}
@Target(ElementType.METHOD)
@interface Operation {
String name();
boolean transactional() default true;
}
@Target(ElementType.FIELD)
@interface Column {
String name();
boolean nullable() default true;
}
@Target(ElementType.PARAMETER)
@interface NotNull {
String message() default "Parameter cannot be null";
}
@Target(ElementType.TYPE_USE)
@interface NonNull {
// For type checking
}
// Using the targeted annotations
@ClassInfo(author = "Alice", version = "1.0")
class UserService {
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "username")
private String username;
public UserService(@NotNull String initialUser) {
this.username = initialUser;
}
@Operation(name = "createUser", transactional = true)
public void createUser(@NotNull String username, @NotNull String email) {
this.username = username;
System.out.println("Creating user: " + username);
}
@Operation(name = "deleteUser")
public void deleteUser(Long userId) {
System.out.println("Deleting user: " + userId);
}
// Using TYPE_USE annotation
public @NonNull String getUserName() {
return username;
}
public void setUserName(@NonNull String username) {
this.username = username;
}
}
public class TargetExample {
public static void main(String[] args) throws NoSuchMethodException {
Class<UserService> serviceClass = UserService.class;
// Class-level annotation
ClassInfo classInfo = serviceClass.getAnnotation(ClassInfo.class);
if (classInfo != null) {
System.out.println("Class Author: " + classInfo.author());
System.out.println("Class Version: " + classInfo.version());
}
// Method annotations
Method createUserMethod = serviceClass.getMethod("createUser", String.class, String.class);
Operation operation = createUserMethod.getAnnotation(Operation.class);
if (operation != null) {
System.out.println("\nMethod Operation: " + operation.name());
System.out.println("Transactional: " + operation.transactional());
}
// Parameter annotations
Annotation[][] paramAnnotations = createUserMethod.getParameterAnnotations();
for (int i = 0; i < paramAnnotations.length; i++) {
for (Annotation annotation : paramAnnotations[i]) {
if (annotation instanceof NotNull) {
System.out.println("Parameter " + i + " has @NotNull: " +
((NotNull) annotation).message());
}
}
}
// Field annotations
for (java.lang.reflect.Field field : serviceClass.getDeclaredFields()) {
Column column = field.getAnnotation(Column.class);
if (column != null) {
System.out.println("\nField: " + field.getName());
System.out.println("Column: " + column.name());
System.out.println("Nullable: " + column.nullable());
}
}
}
}
Output:
Class Author: Alice Class Version: 1.0 Method Operation: createUser Transactional: true Parameter 0 has @NotNull: Parameter cannot be null Parameter 1 has @NotNull: Parameter cannot be null Field: userId Column: user_id Nullable: false Field: username Column: username Nullable: true
Annotation Parameters
Parameter Types and Defaults:
import java.lang.annotation.*;
import java.util.*;
// Annotation with various parameter types
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface TestConfig {
// Primitive types
int timeout() default 30; // seconds
boolean enabled() default true;
// String
String name();
String description() default "";
// Class
Class<?> expectedException() default None.class;
// Enum
Priority priority() default Priority.MEDIUM;
// Array
String[] tags() default {};
// Annotation
Author author() default @Author("Unknown");
// Special marker for no exception
class None extends Throwable {
private None() {}
}
}
// Enum for priority
enum Priority {
LOW, MEDIUM, HIGH, CRITICAL
}
// Nested annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Author {
String value();
String email() default "";
}
// Using the annotation with different parameter combinations
class TestConfigurationExample {
@TestConfig(
name = "userCreationTest",
timeout = 60,
priority = Priority.HIGH,
tags = {"user", "creation", "integration"},
author = @Author(value = "Bob", email = "[email protected]")
)
public void testUserCreation() {
System.out.println("Testing user creation...");
}
@TestConfig(
name = "quickTest",
description = "A quick sanity check",
expectedException = IllegalArgumentException.class
)
public void testWithException() {
System.out.println("Testing exception case...");
throw new IllegalArgumentException("Expected exception");
}
@TestConfig(
name = "disabledTest",
enabled = false
)
public void disabledTest() {
System.out.println("This test is disabled");
}
// Using default values for everything
@TestConfig(name = "defaultTest")
public void defaultTest() {
System.out.println("Test with default values");
}
}
public class ParameterExample {
public static void main(String[] args) throws NoSuchMethodException {
Class<TestConfigurationExample> testClass = TestConfigurationExample.class;
// Process each method with @TestConfig annotation
for (Method method : testClass.getDeclaredMethods()) {
TestConfig config = method.getAnnotation(TestConfig.class);
if (config != null) {
System.out.println("\n=== " + method.getName() + " ===");
System.out.println("Name: " + config.name());
System.out.println("Description: " + config.description());
System.out.println("Timeout: " + config.timeout() + "s");
System.out.println("Enabled: " + config.enabled());
System.out.println("Priority: " + config.priority());
System.out.println("Expected Exception: " + config.expectedException().getSimpleName());
System.out.println("Tags: " + Arrays.toString(config.tags()));
Author author = config.author();
System.out.println("Author: " + author.value() +
(author.email().isEmpty() ? "" : " (" + author.email() + ")"));
// Simulate test execution based on configuration
if (config.enabled()) {
System.out.println("✅ Test would be executed");
} else {
System.out.println("❌ Test is disabled");
}
}
}
}
}
Output:
=== testUserCreation === Name: userCreationTest Description: Timeout: 60s Enabled: true Priority: HIGH Expected Exception: None Tags: [user, creation, integration] Author: Bob ([email protected]) ✅ Test would be executed === testWithException === Name: quickTest Description: A quick sanity check Timeout: 30s Enabled: true Priority: MEDIUM Expected Exception: IllegalArgumentException Tags: [] Author: Unknown ✅ Test would be executed === disabledTest === Name: disabledTest Description: Timeout: 30s Enabled: false Priority: MEDIUM Expected Exception: None Tags: [] Author: Unknown ❌ Test is disabled === defaultTest === Name: defaultTest Description: Timeout: 30s Enabled: true Priority: MEDIUM Expected Exception: None Tags: [] Author: Unknown ✅ Test would be executed
Repeatable Annotations
Java 8+ Repeatable Annotations:
import java.lang.annotation.*;
import java.util.*;
// Container annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Roles {
Role[] value();
}
// Repeatable annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(Roles.class)
@interface Role {
String name();
String[] permissions() default {};
}
// Pre-Java 8 way (without @Repeatable)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface OldRoles {
String[] value();
}
// Using repeatable annotations (Java 8+)
@Role(name = "ADMIN", permissions = {"READ", "WRITE", "DELETE"})
@Role(name = "USER", permissions = {"READ"})
@Role(name = "GUEST", permissions = {"READ_PUBLIC"})
class SecureService {
public void adminOperation() {
System.out.println("Admin operation");
}
public void userOperation() {
System.out.println("User operation");
}
}
// Old way (pre-Java 8)
@OldRoles({"ADMIN", "USER", "GUEST"})
class OldSecureService {
// ...
}
public class RepeatableExample {
public static void main(String[] args) {
Class<SecureService> serviceClass = SecureService.class;
// Get roles using container annotation
Roles rolesContainer = serviceClass.getAnnotation(Roles.class);
if (rolesContainer != null) {
System.out.println("=== Roles using Container ===");
for (Role role : rolesContainer.value()) {
printRole(role);
}
}
// Get roles directly (Java 8+)
System.out.println("\n=== Roles Directly ===");
Role[] rolesDirect = serviceClass.getAnnotationsByType(Role.class);
for (Role role : rolesDirect) {
printRole(role);
}
// Demonstrate the difference
System.out.println("\n=== Annotation Details ===");
Annotation[] annotations = serviceClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("Annotation: " + annotation.annotationType().getSimpleName());
if (annotation instanceof Roles) {
System.out.println(" - This is the container annotation");
} else if (annotation instanceof Role) {
System.out.println(" - This is a repeatable annotation");
}
}
}
private static void printRole(Role role) {
System.out.println("Role: " + role.name());
System.out.println(" Permissions: " + Arrays.toString(role.permissions()));
}
}
Output:
=== Roles using Container === Role: ADMIN Permissions: [READ, WRITE, DELETE] Role: USER Permissions: [READ] Role: GUEST Permissions: [READ_PUBLIC] === Roles Directly === Role: ADMIN Permissions: [READ, WRITE, DELETE] Role: USER Permissions: [READ] Role: GUEST Permissions: [READ_PUBLIC] === Annotation Details === Annotation: Roles - This is the container annotation
Annotation Processing
Runtime Annotation Processing:
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
// Custom annotations for validation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface NotNull {
String message() default "Field cannot be null";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Size {
int min() default 0;
int max() default Integer.MAX_VALUE;
String message() default "Size constraint violated";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Email {
String message() default "Invalid email format";
}
// Validation processor
class Validator {
public static List<String> validate(Object obj) {
List<String> errors = new ArrayList<>();
Class<?> clazz = obj.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
try {
Object value = field.get(obj);
// Check @NotNull
if (field.isAnnotationPresent(NotNull.class)) {
NotNull notNull = field.getAnnotation(NotNull.class);
if (value == null) {
errors.add(field.getName() + ": " + notNull.message());
}
}
// Check @Size
if (field.isAnnotationPresent(Size.class) && value != null) {
Size size = field.getAnnotation(Size.class);
int length = value.toString().length();
if (length < size.min() || length > size.max()) {
errors.add(field.getName() + ": " + size.message() +
" (min: " + size.min() + ", max: " + size.max() +
", actual: " + length + ")");
}
}
// Check @Email
if (field.isAnnotationPresent(Email.class) && value != null) {
String email = value.toString();
if (!isValidEmail(email)) {
Email emailAnnotation = field.getAnnotation(Email.class);
errors.add(field.getName() + ": " + emailAnnotation.message() +
" (" + email + ")");
}
}
} catch (IllegalAccessException e) {
errors.add("Cannot access field: " + field.getName());
}
}
return errors;
}
private static boolean isValidEmail(String email) {
return email != null && email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
}
// Class to validate
class User {
@NotNull(message = "Username is required")
@Size(min = 3, max = 20, message = "Username must be 3-20 characters")
private String username;
@NotNull
@Email
private String email;
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;
private String optionalField;
public User(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
// Getters and setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
public class AnnotationProcessingExample {
public static void main(String[] args) {
// Test cases
User[] testUsers = {
new User("jo", "invalid-email", "short"), // Multiple errors
new User(null, "[email protected]", "validpassword123"), // Null username
new User("validuser", "invalid", "validpassword123"), // Invalid email
new User("validuser", "[email protected]", "validpassword123") // Valid
};
for (int i = 0; i < testUsers.length; i++) {
System.out.println("\n=== Test User " + (i + 1) + " ===");
System.out.println("Username: " + testUsers[i].getUsername());
System.out.println("Email: " + testUsers[i].getEmail());
System.out.println("Password: " + testUsers[i].getPassword());
List<String> errors = Validator.validate(testUsers[i]);
if (errors.isEmpty()) {
System.out.println("✅ Validation passed!");
} else {
System.out.println("❌ Validation errors:");
errors.forEach(error -> System.out.println(" - " + error));
}
}
// Demonstrate reflection with annotations
System.out.println("\n=== Reflection Analysis ===");
Class<User> userClass = User.class;
for (Field field : userClass.getDeclaredFields()) {
System.out.println("\nField: " + field.getName());
System.out.println(" Type: " + field.getType().getSimpleName());
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(" Annotation: @" + annotation.annotationType().getSimpleName());
}
}
}
}
Output:
=== Test User 1 === Username: jo Email: invalid-email Password: short ❌ Validation errors: - username: Username must be 3-20 characters (min: 3, max: 20, actual: 2) - email: Invalid email format (invalid-email) - password: Size constraint violated (min: 8, max: 2147483647, actual: 5) === Test User 2 === Username: null Email: [email protected] Password: validpassword123 ❌ Validation errors: - username: Username is required === Test User 3 === Username: validuser Email: invalid Password: validpassword123 ❌ Validation errors: - email: Invalid email format (invalid) === Test User 4 === Username: validuser Email: [email protected] Password: validpassword123 ✅ Validation passed! === Reflection Analysis === Field: username Type: String Annotation: @NotNull Annotation: @Size Field: email Type: String Annotation: @NotNull Annotation: @Email Field: password Type: String Annotation: @Size Field: optionalField Type: String
Best Practices
1. Use Meaningful Names:
// ✅ Good - clear and descriptive
@interface CacheConfig {
int duration();
TimeUnit unit();
boolean refreshable();
}
// ❌ Avoid - vague names
@interface Config {
int value();
String data();
}
2. Provide Sensible Defaults:
// ✅ Good - sensible defaults
@interface ApiEndpoint {
String path();
String method() default "GET";
boolean secure() default true;
int timeout() default 30;
}
// ❌ Avoid - no defaults when they make sense
@interface BadEndpoint {
String path();
String method(); // Should have default
boolean secure(); // Should have default
}
3. Use Enums for Fixed Values:
// ✅ Good - using enums
enum HttpMethod { GET, POST, PUT, DELETE, PATCH }
@interface RequestMapping {
String path();
HttpMethod method() default HttpMethod.GET;
}
// ❌ Avoid - using strings for fixed values
@interface BadMapping {
String path();
String method(); // Could be any string
}
4. Document Your Annotations:
/**
* Marks a method as an API endpoint that can be called remotely
*
* @author Developer
* @version 1.0
* @since 2024
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ApiMethod {
/**
* The HTTP path for this endpoint
* @return the endpoint path
*/
String value();
/**
* HTTP method to use
* @return the HTTP method
*/
HttpMethod method() default HttpMethod.GET;
/**
* Whether authentication is required
* @return true if authentication is required
*/
boolean requiresAuth() default true;
}
Complete Examples
Complete Framework Example:
```java
import java.lang.annotation.; import java.lang.reflect.;
import java.util.*;
// Framework annotations
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Service {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Command {
String value();
String description() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Inject {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Scheduled {
long fixedRate() default -1;
String cron() default "";
}
// Service classes
@Service(name = "userService")
class UserService {
@Command("createUser")
public String createUser(String username, String email) {
return "User created: " + username + " (" + email + ")";
}
@Command("deleteUser")
public String deleteUser(String username) {
return "User deleted: " + username;
}
@Scheduled(fixedRate = 5000)
public void cleanupInactiveUsers() {
System.out.println("Cleaning up inactive users...");
}
}
@Service(name = "emailService")
class EmailService {
@Inject
private UserService userService;
@Command("sendEmail")
public String sendEmail(String to, String subject) {
return "Email sent to: " + to + " - " + subject;
}
@Scheduled(cron = "0 0 9 * * *") // Daily at 9 AM
public void sendDailyReports() {
System.out.println("Sending daily reports...");
}
}
// Simple DI Container and Framework
class ApplicationContext {
private Map services = new HashMap<>();
private Map commands = new HashMap<>();
public ApplicationContext(Class<?>... configClasses) {
// Scan and instantiate services
for (Class<?> configClass : configClasses) {
scanPackage(configClass.getPackage());
}
// Inject dependencies
injectDependencies();
// Register commands
registerCommands();
}
private void scanPackage(Package pkg) {
// In real implementation, you would scan the package
// For this example, we'll manually register our services
registerService(new UserService());
registerService(new EmailService());
}
private void registerService(Object service) {
Class<?> serviceClass = service.getClass();
Service serviceAnnotation = serviceClass.getAnnotation(Service.class);
String serviceName = serviceAnnotation != null && !serviceAnnotation.name().isEmpty()
? serviceAnnotation.name()
: serviceClass.getSimpleName();
services.put(serviceName, service);
System.out.println("Registered service: " + serviceName);
}
private void injectDependencies() {
for (Object service : services.values()) {
for (Field field : service.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Inject.class)) {
Inject inject = field.getAnnotation(Inject.class);
String dependencyName = inject.value().isEmpty()
? field.getType().getSimpleName()
: inject.value();
Object dependency = services.get(dependencyName);
if (dependency != null) {
field.setAccessible(true);
try {
field.set(service, dependency);
System.out.println("Injected " + dependencyName + " into " +
service.getClass().getSimpleName());
} catch (IllegalAccessException e) {
System.err.println("Failed to inject " + dependencyName + ": " + e.getMessage());
}
}
}
}
}
}
private void registerCommands() {
for (Object service : services.values()) {
for (Method method : service.getClass().getDeclaredMethods()) {
Command command = method.getAnnotation(Command.class);
if (command != null) {
commands.put(command.value(), method);
System.out.println("Registered command: " + command.value() +
" - " + command.description());
}
}
}
}
public Object executeCommand(String commandName, Object... args) {
Method method = commands.get(commandName);
if (method == null) {
throw new IllegalArgumentException("Unknown command: " + commandName);
}
try {
Object service = services.values().stream()
.