Lesson 32 • Advanced
Java Annotations
Annotations are Java's metadata system — the @ symbols you see everywhere in modern Java. They don't change what your code does, but they tell tools and frameworks how to treat it. Master annotations, and you'll understand how Spring, JPA, and JUnit actually work.
Before You Start
You should be comfortable with Reflection API (reading classes at runtime) and Interfaces (since annotations use @interface syntax). Familiarity with Spring or JUnit helps but isn't required.
What You'll Learn
- ✅ Built-in annotations: @Override, @Deprecated, @SuppressWarnings
- ✅ Creating custom annotations with @interface
- ✅ Meta-annotations: @Target, @Retention, @Documented
- ✅ Runtime annotation processing with reflection
- ✅ Compile-time processing with annotation processors
- ✅ Real-world annotations in Spring, JPA, and JUnit
1️⃣ Built-in Annotations
Analogy: Annotations are like sticky notes you attach to your code. They don't change what the code does — they're instructions for someone (or something) else. @Override tells the compiler "check this for me." @Autowired tells Spring "inject a dependency here."
| Annotation | Purpose | What Happens |
|---|---|---|
| @Override | Verify method overrides parent | Compile error if no matching parent method |
| @Deprecated | Mark as obsolete | Compiler warning when used by others |
| @SuppressWarnings | Silence specific warnings | @SuppressWarnings("unchecked") |
| @FunctionalInterface | Exactly one abstract method | Compile error if more than one |
| @SafeVarargs | Suppress heap pollution warning | Safe generic varargs methods |
Try It: Built-in Annotations
// 💡 Try modifying this code and see what happens!
// Java Built-in Annotations — Simulated in JavaScript
console.log("=== Built-in Annotations ===\n");
// Simulate @Override check
class Animal {
speak() { return "..."; }
}
class Dog extends Animal {
speak() { return "Woof!"; } // @Override — correct
}
class Cat extends Animal {
speak() { return "Meow!"; } // @Override — correct
}
// Test overrides
let dog = new Dog();
let cat = new Cat();
console.log("Dog.speak(): " + dog.speak() +
...2️⃣ Creating Custom Annotations
Custom annotations let you create your own metadata vocabulary. The key is understanding meta-annotations — annotations that describe how your annotation works:
| Meta-Annotation | Controls | Values |
|---|---|---|
| @Target | Where it can be applied | TYPE, METHOD, FIELD, PARAMETER, etc. |
| @Retention | How long it's kept | SOURCE → CLASS → RUNTIME |
| @Documented | Include in Javadoc | Marker annotation (no values) |
| @Inherited | Subclasses inherit it | Only for class-level annotations |
| @Repeatable | Can be used multiple times | Requires container annotation |
Critical: @Retention(RUNTIME) is required if you want to read the annotation via reflection. Without it, the annotation is discarded before your code can see it!
Try It: Custom Annotation Processing
// 💡 Try modifying this code and see what happens!
// Custom Annotation — Simulated @Timer annotation processor
console.log("=== Custom Annotation Processing ===\n");
// Simulate annotation metadata
const annotations = new Map();
function Timer(options = {}) {
return function(target, name) {
annotations.set(name, { type: "Timer", unit: options.unit || "ms", log: options.log !== false });
};
}
// "Annotated" class
class DataProcessor {
processOrders() {
let sum = 0;
for (le
...Common Mistakes
- ⚠️ Forgetting @Retention(RUNTIME) — your annotation won't be visible via reflection, and you'll spend hours debugging
- ⚠️ Overusing @SuppressWarnings — it hides real problems. Fix the warning instead
- ⚠️ Confusing @interface with interface —
@interfacedefines an annotation,interfacedefines a contract - ⚠️ Annotation values must be compile-time constants — no method calls, no variables, only literals, enums, class literals
Pro Tips
- 💡 Spring: @Component, @Autowired, @RequestMapping, @Transactional — the entire framework is annotation-driven
- 💡 JPA/Hibernate: @Entity, @Table, @Column, @Id — map Java objects to database tables
- 💡 JUnit 5: @Test, @BeforeEach, @DisplayName, @ParameterizedTest — test discovery and lifecycle
- 💡 Lombok: @Data, @Builder, @Getter — generates boilerplate at compile time via annotation processing
Try It: Framework Annotation Simulator
// 💡 Try modifying this code and see what happens!
// Simulate how Spring-style annotations work under the hood
console.log("=== Framework Annotation Simulator ===\n");
// Simulated Spring container
const container = {};
const routes = [];
function Component(name) {
return function(cls) { container[name] = new cls(); };
}
function RequestMapping(method, path) {
return function(target, name) {
routes.push({ method, path, handler: name, controller: target.constructor?.name || "?" });
...📋 Quick Reference
| Concept | Syntax | Purpose |
|---|---|---|
| Define annotation | public @interface Name {} | Custom metadata |
| Target | @Target(ElementType.METHOD) | Where to apply |
| Retention | @Retention(RUNTIME) | When available |
| Read at runtime | method.getAnnotation(X.class) | Reflection access |
| Repeatable | @Repeatable(Container.class) | Use multiple times |
🎉 Lesson Complete!
You've mastered Java Annotations — the foundation of declarative programming in Java! Next: IO & NIO — file handling, streams, and non-blocking I/O.
Sign up for free to track which lessons you've completed and get learning reminders.