Creating and Processing Custom Annotations in Java
Fast recall
Quick revision
Core answer
METHOD, TYPE, FIELD, PARAMETER, etc.), `@Retention` controls lifetime (SOURCE — compile-time only, discarded; CLASS — in bytecode but not at runtime; RUNTIME — available via reflection), `@Documented` includes it in Javadoc, and `@Inherited` makes it transfer to subclasses. Annotations can declare parameters as methods with optional defaults — String value() default "";. Processing happens at one of three times: compile-time via an AbstractProcessor (APT) — used by Lombok, AutoValue, MapStruct; class-load time via bytecode manipulation (AspectJ, ASM); runtime via reflection — required for Spring, Jackson, Hibernate. Choose RetentionPolicy.RUNTIME when frameworks need to read it via reflection; SOURCE for APT-only annotations; CLASS is rarely useful.Whether you know the meta-annotations (@Target, @Retention, @Documented, @Inherited), the three retention policies, and the three processing approaches
Using SOURCE retention for an annotation that frameworks need at runtime; forgetting that @Inherited only applies to class-level annotations (not methods/fields)
Distinguish the three processing times (compile-time APT, class-load bytecode, runtime reflection); cite concrete examples (Lombok=APT, AspectJ=bytecode, Spring=runtime reflection)
Interview-ready guide
Interview answer
You declare a custom annotation with `@interface`. Parameters are declared as methods with optional defaults — String value() default "";. The single-parameter-named-value convention enables the shorthand @MyAnn("foo").
The configuration comes from meta-annotations. `@Target` specifies where it can be applied — METHOD, TYPE, FIELD, PARAMETER. `@Retention` controls lifetime — SOURCE discards it at compile time, CLASS keeps it in bytecode but not at runtime, RUNTIME keeps it available to reflection. `@Documented` includes it in Javadoc. `@Inherited` makes it transfer to subclasses — but only for class-level annotations, not methods or fields.
The retention choice matters. Spring, Jackson, JPA, JUnit — all use reflection to read annotations at runtime, so their custom annotations need `RUNTIME` retention. Annotation processors like Lombok, AutoValue, and Dagger read annotations at compile time and generate code, so they use `SOURCE` retention — the annotation isn't even in the bytecode. Using the wrong retention breaks the framework.
Processing an annotation happens in one of three ways.
First, runtime reflection — Spring, Hibernate, Jackson. You iterate methods or fields and call getAnnotation(MyAnn.class). Simple, flexible, slightly slower at runtime.
Second, compile-time annotation processing — APT. You extend AbstractProcessor, declare @SupportedAnnotationTypes, and generate new Java source files using tools like JavaPoet. Lombok, MapStruct, Dagger, AutoValue all use this approach. Zero runtime cost because the processing is done before the program ever runs.
Third, bytecode manipulation — AspectJ, ASM, ByteBuddy. The tool directly modifies .class files after compilation. Most powerful, most complex.
For most custom annotations you'd write in an application, you want RUNTIME retention plus Spring AOP — define an @Aspect with @Around("@annotation(myAnn)") and your logic runs whenever a method with that annotation is called.
Creating and Processing Custom Annotations in Java
Direct Answer
Declare a custom annotation with @interface, and use meta-annotations to configure its behavior: @Target specifies where it can be placed (METHOD, TYPE, FIELD, PARAMETER, etc.), @Retention controls lifetime (SOURCE — compile-time only, discarded; CLASS — in bytecode but not at runtime; RUNTIME — available via reflection), @Documented includes it in Javadoc, and @Inherited makes it transfer to subclasses. Annotations can declare parameters as methods with optional defaults — String value() default "";. Processing happens at one of three times: compile-time via an AbstractProcessor (APT) — used by Lombok, AutoValue, MapStruct; class-load time via bytecode manipulation (AspectJ, ASM); runtime via reflection — required for Spring, Jackson, Hibernate. Choose RetentionPolicy.RUNTIME when frameworks need to read it via reflection; SOURCE for APT-only annotations; CLASS is rarely useful.
Common Mistakes & Pitfalls
Using SOURCE retention for an annotation that frameworks need at runtime; forgetting that @Inherited only applies to class-level annotations (not methods/fields)
Check your understanding
Answer these without looking back at the article.
- What's the difference between `@Retention(CLASS)` and `@Retention(RUNTIME)`?
- Why is `@Inherited` ignored on method-level annotations?
- How does Spring's `AnnotatedElementUtils` handle composed annotations?
- What are the advantages of compile-time APT over runtime reflection for annotation processing?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.