← Back to Advanced Java Course | Chapter 11: Advanced & Security | Lesson 2 of 19

Java Annotations Advanced

Creating Custom Annotations

Custom annotations let you attach structural metadata tags to your code that tools, compilers, or runtime frameworks can later read and act on, without that metadata changing the actual runtime behavior of the annotated code by itself.

Example: Creating Custom Annotations

java
import java.lang.annotation.*;
public class Main {
	@interface Info { String value(); } // structural metadata, no runtime behavior on its own
	@Info("demo")
	static class Sample {}
	public static void main(String[] args) {
		System.out.println("Custom annotation @Info declared");
	}
}

Retention Policy levels

The @Retention policy controls how long an annotation's metadata survives: SOURCE annotations are stripped by the compiler and never make it into the .class file, while RUNTIME annotations are preserved so they can be read via reflection while the program is executing.

Example: Retention Policy levels

java
import java.lang.annotation.*;
public class Main {
	@Retention(RetentionPolicy.RUNTIME) // preserved so reflection can read it while running
	@interface Tag { String value(); }
	@Tag("example")
	static class Sample {}
	public static void main(String[] args) {
		Tag tag = Sample.class.getAnnotation(Tag.class);
		System.out.println(tag.value());
	}
}

Targeting Annotation Elements

The @Target annotation restricts where a custom annotation is allowed to be placed -- for example, limiting it to fields only, or methods only -- so the compiler can catch misuse (like applying a field-only annotation to a class) instead of silently allowing it.

Example: Targeting Annotation Elements

java
import java.lang.annotation.*;
public class Main {
	@Target(ElementType.FIELD) // restricted to fields only
	@Retention(RetentionPolicy.RUNTIME)
	@interface Required {}
	static class Sample {
		@Required
		String name;
	}
	public static void main(String[] args) {
		System.out.println("@Required can only be placed on fields");
	}
}

Annotation Inheritance

Annotations are not automatically inherited by subclasses the way fields and methods are, unless the annotation's own declaration is itself marked @Inherited. This surprises a lot of developers who assume annotation behavior mirrors regular Java inheritance.

Example: Annotation Inheritance

java
import java.lang.annotation.*;
public class Main {
	@Inherited
	@Retention(RetentionPolicy.RUNTIME)
	@interface Marker {}
	@Marker
	static class Parent {}
	static class Child extends Parent {}
	public static void main(String[] args) {
		System.out.println(Child.class.isAnnotationPresent(Marker.class)); // true, because @Inherited was used
	}
}

Annotation Processing

At runtime, annotation processors use reflection to scan for annotated elements and act on them -- validating object state, mapping fields to database columns, or registering routes -- which is the mechanism that lets frameworks like JPA and Spring translate your annotations into real behavior.

Example: Annotation Processing

java
import java.lang.annotation.*;
import java.lang.reflect.*;
public class Main {
	@Retention(RetentionPolicy.RUNTIME)
	@interface Column { String name(); }
	static class User {
		@Column(name = "user_name")
		String name;
	}
	public static void main(String[] args) throws Exception {
		Field field = User.class.getDeclaredField("name");
		Column column = field.getAnnotation(Column.class);
		System.out.println("Maps to DB column: " + column.name()); // annotation processor scanning at runtime
	}
}

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.