Reflection: how frameworks read your classes
Reflection lets code inspect and use classes at run time, without knowing them at compile time.
- Get a Class object: obj.getClass(), Order.class or Class.forName("com.shop.Order").
- Inspect it: getDeclaredFields(), getDeclaredMethods(), getDeclaredConstructors(), getAnnotation(), getSuperclass(), isRecord().
- Use it: field.get/set, method.invoke, constructor.newInstance. Private members need setAccessible(true).
This is how frameworks work without you writing glue code: Spring creates and injects beans, JUnit finds @Test methods, Jackson maps JSON to fields, Hibernate reads @Entity classes, and validation reads @NotBlank.
In your own application code, prefer normal method calls: reflection is slower, skips compile-time checks, breaks with refactoring and can bypass encapsulation.
Example
@Retention(RetentionPolicy.RUNTIME) // keep the annotation available at run time
@Target(ElementType.FIELD)
@interface NotBlank {}
record Signup(@NotBlank String name, @NotBlank String email, String referralCode) {}
static List<String> validate(Object target) throws IllegalAccessException {
List<String> errors = new ArrayList<>();
for (Field field : target.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(NotBlank.class)) {
field.setAccessible(true); // fields of a record are private
Object value = field.get(target);
if (value == null || value.toString().isBlank()) {
errors.add(field.getName() + " must not be blank");
}
}
}
return errors;
}
System.out.println(validate(new Signup("Asha", " ", null))); // [email must not be blank]Class<?> type = Class.forName("java.util.ArrayList");
Object list = type.getDeclaredConstructor().newInstance(); // like new ArrayList<>()
Method add = type.getMethod("add", Object.class);
add.invoke(list, "hello");
System.out.println(list); // [hello]Common mistake
Using reflection to reach private internals of libraries. It breaks on upgrades and is blocked for JDK classes by strong encapsulation.
Under the hood
For annotations to be visible to reflection they need @Retention(RUNTIME); the default (CLASS) keeps them in the .class file but not at run time. Since Java 16 the JDK's internals are strongly encapsulated, so setAccessible(true) on JDK private members throws InaccessibleObjectException unless the module is explicitly opened. MethodHandles (java.lang.invoke) are a faster, safer alternative for dynamic calls, and frameworks increasingly generate code at build time instead of reflecting at startup (for faster startup and native images).
Check yourself
Which call reads a private field's value via reflection?
How this connects
Know these first
Where this leads
You've reached the end of this thread. Try a learning path for what's next.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.