Skip to content
Anand Creations AI
All posts

Java Annotations: A Comprehensive Guide

A focused guide to Java Annotations. Definition, creation, and usage through practical examples. Covers built-in annotations, custom annotation design, reflection techniques, and best practices.

Introduction to Java Annotations

Java Annotations are metadata tags that provide additional information about a program. They start with @, followed by the annotation name, and can be placed above:

  • Classes
  • Interfaces
  • Methods
  • Method parameters
  • Fields
  • Local variables

Built-in Java Annotations

Java provides several built-in annotations:

  • @Override: Indicates that a method is intended to override a method in a superclass
  • @Deprecated: Marks code as obsolete or no longer recommended
  • @SuppressWarnings: Suppresses specific compiler warnings
  • @Contended: Used for preventing false sharing in concurrent programming

Purpose of Creating Custom Annotations

Custom annotations serve multiple purposes:

  1. Passive Documentation: Provide additional context and metadata about code
  2. Source Code Processing: Supply input for Java source code processors
  3. Runtime Reflection: Enable runtime access to annotation information via Java Reflection

Annotation Types and Purposes

Java Annotations can be used for three main types of instructions:

  1. Compiler Instructions: Provide guidance to the compiler
  2. Build-time Instructions: Assist in build processes and code generation
  3. Runtime Instructions: Enable runtime behavior modification

Creating a Custom Annotation

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PerformanceLog {
    String value() default "";
    int sampleRate() default 100;
}

The two meta-annotations doing the work:

  • @Retention(RUNTIME): keep this annotation in the class file and make it available via reflection at runtime
  • @Target(METHOD): restrict this annotation to method declarations only

Using Reflection to Read Annotations

Method method = service.getClass().getMethod("processOrder");
PerformanceLog log = method.getAnnotation(PerformanceLog.class);
if (log != null) {
    System.out.printf("Sampling at %d%% (tag: %s)%n",
        log.sampleRate(), log.value());
}

This is the bridge between declarative metadata and runtime behaviour. Frameworks like Spring, Jackson, JUnit, and JPA are largely built on top of this pattern.

Best Practices

  • Prefer composition over inheritance for behaviour. Annotations are great for declaring the behaviour.
  • Don’t overuse runtime reflection. It’s slower than direct calls. Cache the annotation lookups when used in hot paths.
  • Document custom annotations like APIs. Others will use them based on the contract, not the implementation.
  • Avoid magical annotations that hide major side effects. If an @TransactionalAndAlsoSendsEmail annotation exists, split it.

When Annotations Are the Wrong Tool

If the metadata changes at runtime, you don’t want annotations. You want configuration. Annotations are compile-time constants. Use them for static facts about code; use config/DI for behaviour that needs to vary by environment.

Behind on AI and not sure where to start?

Book a 30-minute call. I'll tell you what's worth building, what isn't, and whether I'm the right person for it.