Java Annotations Explained with Examples

A Java annotation attaches structured metadata to a declaration, type use, or other supported program location.

Lesson 86 of 127Advanced13 min read

A Java annotation attaches structured metadata to a declaration, type use, or other supported program location. It does not run like an ordinary method. Instead, a compiler, annotation processor, framework, or runtime inspection tool may read that metadata and act on it. @Override helps the compiler check your intention, while a custom annotation can describe a field or method for a tool. The behavior depends on the annotation's contract and the component that interprets it.

Use the standard annotations deliberately

@Override asks the compiler to verify that a method overrides or implements a suitable inherited method. It catches errors such as changing a parameter type when you intended to override. @Deprecated marks an API that callers should avoid and can include a reason in documentation. @SuppressWarnings silences selected compiler diagnostics; it does not repair the underlying issue. Use it narrowly and understand the specific warning before suppressing it.

Declare an annotation type

An annotation declaration uses @interface. Its elements resemble parameterless method declarations and can have default values. Element types are restricted to supported metadata forms, including primitives, String, Class, enums, annotations, and arrays of those types. You cannot place an arbitrary mutable object in an annotation. Applying the annotation supplies values for required elements, creating declarative metadata that tools can inspect consistently across many classes.

Control where metadata is allowed

@Target limits the locations where an annotation can appear, such as methods, fields, types, or parameters. A method-only annotation placed on a field is rejected at compile time. Use the narrowest target that expresses the annotation's meaning. An annotation documenting a measured unit for a field should not casually be accepted on every declaration. Target constraints make misuse visible before a framework encounters the code.

Select the retention policy

@Retention determines how long metadata is retained. SOURCE annotations are discarded after compilation. CLASS annotations are stored in class files but are not normally visible through runtime reflection; CLASS is the default. RUNTIME annotations remain available to reflection APIs. Choose runtime retention only when a runtime consumer actually needs it. The annotation example needs RUNTIME because main reads the metadata from a Method object.

Separate metadata from its consumer

Applying @Label to a method does not automatically print a label or execute the method. The reader must request the annotation and decide what to do with it. Frameworks often hide this step behind scanning and registration, which can make annotations look magical. When debugging an annotation-based feature, check both the metadata and the configuration that discovers it. Correct spelling alone cannot make an unregistered processor or framework react.

Document and test annotation contracts

A custom annotation should explain its intended consumer, defaults, and restrictions. @Documented can include it in generated API documentation. @Inherited affects class annotation lookup through inheritance and does not simply propagate every annotation on fields or methods. Repeatable annotations require an explicit container definition. Introduce these features only when the metadata model needs them; a small method annotation with a clear runtime reader is a better first example.

Practice tasks and checks

Add a second labeled method to MethodMetadata and use getMethod to read each label. Then look up a public method with no Label and check for null before reading value. This demonstrates why a general annotation reader cannot assume every declaration carries metadata. Change Label's target to TYPE and confirm that placing it on summarize becomes a compilation error; restore the method target afterward. In PriorityMetadata, add a third annotated class with a different explicit priority and explain which value a consumer would observe. Do not claim that this changes scheduling until you write a consumer that orders jobs using those values.

Java Summary

  • Annotations supply metadata interpreted by a compiler or other consumer.
  • Runtime reflection requires RUNTIME retention.
  • Target declarations prevent annotations being used in unsuitable locations.
  • Continue with runtime reflection when these examples are clear.

Java Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Catch an overriding mistake early

Save this program as OverrideGreeting.java. Compile it with javac OverrideGreeting.java and run it with java OverrideGreeting.

Expected output:

EveningGreeter overrides the inherited greeting method. The variable's declared type is Greeter, but ordinary dynamic dispatch selects the subclass implementation. @Override contributes a compile-time check rather than changing dispatch. Renaming the subclass method to greetings while leaving the annotation would fail compilation, exposing the accidental mismatch. Removing the annotation would allow an unrelated method and lose that helpful check.

Java
public class OverrideGreeting {
    static class Greeter {
        String greeting() { return "Hello"; }
    }
    static class EveningGreeter extends Greeter {
        @Override
        String greeting() { return "Good evening"; }
    }
    public static void main(String[] args) {
        Greeter greeter = new EveningGreeter();
        System.out.println(greeter.greeting());
    }
}
Output
Good evening
Example 2

Read a custom runtime annotation

Save this program as MethodMetadata.java. Compile it with javac MethodMetadata.java and run it with java MethodMetadata.

Expected output:

The declaration defines one required String element named value. Its special name allows the concise @Label("ticket summary") syntax. Reflection finds the public summarize method and reads its retained Label metadata. No invocation of summarize occurs: inspecting metadata and running behavior are separate operations. If the annotation were absent, getAnnotation would return null, so a general-purpose reader should check before accessing value.

Java
import java.lang.annotation.*;
import java.lang.reflect.Method;
public class MethodMetadata {
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @interface Label { String value(); }
    @Label("ticket summary")
    public static void summarize() {}
    public static void main(String[] args) throws Exception {
        Method method = MethodMetadata.class.getMethod("summarize");
        Label label = method.getAnnotation(Label.class);
        System.out.println(label.value());
    }
}
Output
ticket summary
Example 3

Use an annotation default

Save this program as PriorityMetadata.java. Compile it with javac PriorityMetadata.java and run it with java PriorityMetadata.

Expected output:

StandardJob omits the element, so reflection observes the declared default three. UrgentJob supplies one explicitly. The integers do not create scheduling behavior on their own; a scheduler would need to interpret them. Keeping that distinction clear prevents the common assumption that naming an annotation Priority automatically affects thread execution or operating-system scheduling.

Java
import java.lang.annotation.*;
public class PriorityMetadata {
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @interface Priority { int value() default 3; }
    @Priority
    static class StandardJob {}
    @Priority(1)
    static class UrgentJob {}
    public static void main(String[] args) {
        System.out.println(StandardJob.class.getAnnotation(Priority.class).value());
        System.out.println(UrgentJob.class.getAnnotation(Priority.class).value());
    }
}
Output
3
1

Java FAQ

Does an annotation change a method's code?

Usually it supplies metadata rather than editing the body. A compiler tool or framework may generate or arrange additional behavior, but that consumer is responsible. Always inspect the annotation's documented processing mechanism.

Why is my annotation invisible to reflection?

Check its retention policy. Without @Retention(RUNTIME), an annotation normally uses CLASS retention and is not returned by runtime annotation lookup. Also confirm you are inspecting the declaration where it was actually placed.

What does @Deprecated do?

It marks a class, method or field that shouldn't be used any more, and the compiler warns any code that still uses it. @Deprecated(since = "3.0", forRemoval = true) adds when it was deprecated and warns that it will be deleted. Explain the replacement in a Javadoc @deprecated tag.

How do frameworks use annotations?

Frameworks read annotations to decide what to do with your code. JUnit runs methods marked @Test, Spring creates and connects classes marked with annotations such as @Service, and JPA maps classes marked @Entity to database tables. They read runtime annotations through reflection, or process them while compiling.

What types can annotation elements have?

Primitive types, String, Class, enums, other annotations, and one-dimensional arrays of those. Values must be constants known at compile time. An element named value can be set without its name, which is why you can write @SuppressWarnings("unchecked").

Java Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choiceeasy

Which retention policy supports getAnnotation at runtime?

Multiple choicemedium

What does @Override verify?

Java Quiz

2 questions to check what you learned.

  1. What makes @Label("text") shorthand valid?

    • Its required element is named value
    • All annotation elements are positional
    • The annotation has no target
  2. Does @Priority(1) automatically schedule a job?

    • No; a consumer must interpret it
    • Yes; the JVM prioritizes all annotated classes
    • Only if the class is static