Skip to content

Annotations in Java

Question

Explain the concept of annotations in Java. How do you create custom annotations, and what are their various uses? What are the best practices for working with annotations?

Answer

Annotations in Java provide metadata about code elements. They can be used for documentation, compilation instructions, and runtime processing. Annotations are a powerful feature that enables declarative programming and reduces boilerplate code.

Built-in Annotations

  1. Documentation Annotations

    /**
     * @author John Doe
     * @version 1.0
     */
    @Deprecated
    public class OldClass {
        @Override
        public String toString() {
            return "Old class";
        }
    }
    

  2. Compilation Annotations

    @SuppressWarnings("unchecked")
    public class WarningExample {
        @FunctionalInterface
        public interface MyFunction {
            void execute();
        }
    }
    

Creating Custom Annotations

  1. Basic Annotation

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    public @interface Log {
        String value() default "";
        Level level() default Level.INFO;
    }
    
    public enum Level {
        INFO, WARNING, ERROR
    }
    
    // Usage
    @Log(level = Level.INFO)
    public class Service {
        @Log("Processing request")
        public void processRequest() {
            // Method implementation
        }
    }
    

  2. Annotation with Multiple Parameters

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Validate {
        int min() default 0;
        int max() default Integer.MAX_VALUE;
        String pattern() default "";
        String message() default "Validation failed";
    }
    
    // Usage
    public class UserService {
        @Validate(min = 18, max = 100, message = "Age must be between 18 and 100")
        public void setAge(int age) {
            // Method implementation
        }
    }
    

Annotation Processing

  1. Runtime Annotation Processing

    public class LogProcessor {
        public static void processAnnotations(Object obj) {
            Class<?> clazz = obj.getClass();
    
            // Process class-level annotations
            Log classLog = clazz.getAnnotation(Log.class);
            if (classLog != null) {
                System.out.println("Class log level: " + classLog.level());
            }
    
            // Process method-level annotations
            for (Method method : clazz.getDeclaredMethods()) {
                Log methodLog = method.getAnnotation(Log.class);
                if (methodLog != null) {
                    System.out.println("Method: " + method.getName() + 
                        ", Log message: " + methodLog.value());
                }
            }
        }
    }
    

  2. Compile-time Annotation Processing

    @SupportedAnnotationTypes("com.example.Validate")
    @SupportedSourceVersion(SourceVersion.RELEASE_11)
    public class ValidationProcessor extends AbstractProcessor {
        @Override
        public boolean process(Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {
            for (Element element : roundEnv.getElementsAnnotatedWith(Validate.class)) {
                Validate validate = element.getAnnotation(Validate.class);
                // Generate validation code
            }
            return true;
        }
    }
    

Common Use Cases

  1. Dependency Injection

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Autowired {
        boolean required() default true;
    }
    
    public class UserService {
        @Autowired
        private UserRepository userRepository;
    
        @Autowired(required = false)
        private CacheService cacheService;
    }
    

  2. Unit Testing

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Test {
        String description() default "";
        Class<? extends Throwable> expected() default None.class;
    }
    
    public class UserTest {
        @Test(description = "Should create user successfully")
        public void testCreateUser() {
            // Test implementation
        }
    
        @Test(expected = IllegalArgumentException.class)
        public void testInvalidUser() {
            // Test implementation
        }
    }
    

Best Practices

  1. Annotation Design

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Configuration {
        // Use primitive types or String for values
        String[] basePackages() default {};
    
        // Use enums for fixed set of values
        Scope scope() default Scope.SINGLETON;
    
        // Use Class for type references
        Class<?>[] exclude() default {};
    }
    
    public enum Scope {
        SINGLETON, PROTOTYPE
    }
    

  2. Annotation Documentation

    /**
     * Indicates that a method is a test case.
     * This annotation can be used to mark methods that should be executed
     * as part of the test suite.
     *
     * @see TestRunner
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Test {
        /**
         * The description of the test case.
         * This will be displayed in test reports.
         */
        String description() default "";
    
        /**
         * The expected exception that should be thrown.
         * If no exception is expected, use None.class.
         */
        Class<? extends Throwable> expected() default None.class;
    }
    

Common Use Cases

  1. Validation Framework

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.PARAMETER})
    public @interface NotNull {
        String message() default "Value cannot be null";
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.PARAMETER})
    public @interface Size {
        int min() default 0;
        int max() default Integer.MAX_VALUE;
        String message() default "Size must be between {min} and {max}";
    }
    
    public class User {
        @NotNull(message = "Name is required")
        private String name;
    
        @Size(min = 3, max = 50)
        private String email;
    }
    

  2. ORM Mapping

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Entity {
        String table() default "";
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Column {
        String name() default "";
        boolean nullable() default true;
    }
    
    @Entity(table = "users")
    public class User {
        @Column(name = "user_id", nullable = false)
        private Long id;
    
        @Column(name = "user_name")
        private String name;
    }
    

Testing

  1. Testing Annotation Processing
    @Test
    public void testLogAnnotation() {
        Service service = new Service();
        LogProcessor.processAnnotations(service);
        // Verify log output
    }
    
    @Test
    public void testValidationAnnotation() {
        User user = new User();
        ValidationProcessor processor = new ValidationProcessor();
        // Test validation logic
    }
    

Common Pitfalls

  1. Annotation Retention

    // Bad - Missing retention policy
    public @interface BadAnnotation {
        String value();
    }
    
    // Good - Explicit retention policy
    @Retention(RetentionPolicy.RUNTIME)
    public @interface GoodAnnotation {
        String value();
    }
    

  2. Annotation Target

    // Bad - Missing target
    @Retention(RetentionPolicy.RUNTIME)
    public @interface BadAnnotation {
        String value();
    }
    
    // Good - Explicit target
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface GoodAnnotation {
        String value();
    }