Annotations & Reflection
Built-in and custom annotations, the reflection API, and how frameworks use them.
Deutsche Übersetzung in Arbeit
Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.
Auf dieser Seite
Ever wondered how Spring wires your app together, or how JUnit finds your test methods? The magic is annotations and reflection - the tools that let code inspect and act on other code. Understanding them demystifies every major Java framework.
Annotations: metadata on code
An annotation is a label you attach to code - a class, method, or field - that carries information for tools, frameworks, or the compiler. You've already used some:
@Override // tells the compiler: this replaces a parent method
@Deprecated // marks something as outdated
@FunctionalInterface // enforces a single abstract methodImagine warehouse boxes with labels: "Fragile", "This Side Up", "Priority". The boxes work fine without them, but the labels tell the handlers how to treat each one. Annotations are labels on your code that tell frameworks and tools how to handle it - without changing what the code itself does.
Defining your own annotation
You can create custom annotations. They can hold values:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) // keep it available at runtime
@Target(ElementType.METHOD) // only allowed on methods
@interface Test {
String description() default "";
}@Retentioncontrols how long the annotation survives:SOURCE(compiler only),CLASS, orRUNTIME(available while the program runs - needed for frameworks).@Targetrestricts where it can be applied (method, field, class…).
class MathTests {
@Test(description = "addition works")
void testAdd() { /* ... */ }
}Reflection: code inspecting code
Reflection lets a program examine and manipulate classes, methods, and fields at runtime - even ones it didn't know about at compile time.
Class<?> clazz = MathTests.class;
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Test.class)) {
System.out.println("Found test: " + method.getName());
}
}Normally you interact with an object through its public methods - you see its surface. Reflection is an X-ray: it lets you peer inside at runtime, discover every field and method, read their annotations, and even invoke them by name. It sees what's normally hidden.
Building a mini test runner
Put them together and you've built the core of a testing framework - the same idea JUnit uses:
Object instance = clazz.getDeclaredConstructor().newInstance();
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Test.class)) {
try {
method.invoke(instance); // run the test method
System.out.println("PASS: " + method.getName());
} catch (Exception e) {
System.out.println("FAIL: " + method.getName());
}
}
}This is genuinely how frameworks work: annotations mark intent, reflection discovers and acts on them.
How frameworks use this
| Framework | Annotation | What reflection does |
|---|---|---|
| Spring | @Autowired | Finds and injects dependencies |
| JUnit | @Test | Discovers and runs test methods |
| JPA/Hibernate | @Entity | Maps classes to database tables |
| Jackson | @JsonProperty | Maps fields to JSON keys |
Powerful, but use sparingly
Reflection is slower than direct calls and bypasses compile-time safety (mistakes surface at runtime). It's essential for frameworks, but in everyday application code you should rarely need it. Reach for normal method calls first.
Quick check
What RetentionPolicy must a custom annotation have for a framework to read it while the program runs?
Key takeaways
- Annotations attach metadata to code for compilers, tools, and frameworks - without changing behavior.
- Custom annotations use @Retention (lifespan) and @Target (where allowed); RUNTIME is needed for frameworks.
- Reflection inspects and manipulates classes, methods, and fields at runtime.
- Together they power frameworks: annotations mark intent, reflection discovers and acts on it.
- Reflection is powerful but slow and unsafe - use it sparingly in ordinary application code.
Next, we go even deeper - inside the JVM itself: class loading, memory, the JIT, and garbage collection.