Reflection Basics in Java: Getting Class Information at Runtime with Reflection

Illustration for Reflection Basics in Java: Getting Class Information at Runtime with Reflection
By Last updated:

A common mistake developers make when using Reflection is confusing compile-time knowledge of a class with runtime inspection. For example, they might try to dynamically load a class without realizing that Reflection offers powerful APIs to inspect class structure, methods, fields, and constructors at runtime. This misunderstanding leads to brittle code that breaks when APIs evolve.

In real-world frameworks like Spring (dependency injection), Hibernate (entity mapping), and JUnit (test discovery), Reflection is used to discover class metadata dynamically at runtime. Without it, these frameworks would need static configuration files or hard-coded mappings.

Think of Reflection as having a blueprint of your house while you’re already inside it. You can walk through rooms (methods), inspect furniture (fields), and check entrances (constructors) without leaving the house. Knowing how to access class information at runtime is the first step in mastering Java Reflection.


Getting Class Information at Runtime

Obtaining the Class Object

There are multiple ways to obtain a Class object:

Class<?> clazz1 = String.class;                  // Using .class syntax
Class<?> clazz2 = Class.forName("java.util.List"); // Using fully qualified name
Class<?> clazz3 = new Integer(5).getClass();      // From an instance

These Class objects are the entry point for all reflection operations.


Getting Class Name and Modifiers

Class<?> clazz = java.util.ArrayList.class;

System.out.println("Class Name: " + clazz.getName());
System.out.println("Simple Name: " + clazz.getSimpleName());
System.out.println("Modifiers: " + java.lang.reflect.Modifier.toString(clazz.getModifiers()));

Output:

Class Name: java.util.ArrayList
Simple Name: ArrayList
Modifiers: public

Inspecting Fields

import java.lang.reflect.Field;

Class<?> clazz = java.util.HashMap.class;
Field[] fields = clazz.getDeclaredFields();

for (Field field : fields) {
    System.out.println(field.getName() + " : " + field.getType());
}

Real-world use: Hibernate inspects entity fields to map them to database columns.


Inspecting Methods

import java.lang.reflect.Method;

Class<?> clazz = String.class;
Method[] methods = clazz.getDeclaredMethods();

for (Method method : methods) {
    System.out.println(method.getName());
}

Real-world use: JUnit scans for methods annotated with @Test to execute them as test cases.


Inspecting Constructors

import java.lang.reflect.Constructor;

Class<?> clazz = String.class;
Constructor<?>[] constructors = clazz.getDeclaredConstructors();

for (Constructor<?> constructor : constructors) {
    System.out.println(constructor);
}

Real-world use: Spring uses constructors to instantiate beans dynamically.


Inspecting Superclass and Interfaces

Class<?> clazz = java.util.HashMap.class;

System.out.println("Superclass: " + clazz.getSuperclass().getName());

Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> i : interfaces) {
    System.out.println("Implements: " + i.getName());
}

Real-world use: Frameworks check for implemented interfaces to apply proxying (e.g., Spring AOP).


📌 What's New in Java Versions?

  • Java 5 – Reflection improved to support annotations.
  • Java 8 – Introduced Executable API and method parameter reflection.
  • Java 9 – Strong encapsulation in modules restricted reflection access (setAccessible(true) warnings).
  • Java 11 – No significant changes.
  • Java 17 – Further encapsulation restrictions in JDK modules.
  • Java 21 – No major reflection updates.

Pitfalls and Best Practices

Pitfalls

  • Accessing private fields without setAccessible(true) throws exceptions.
  • Overusing reflection in performance-critical areas can slow down applications.
  • Misinterpreting modifiers or forgetting to check annotations leads to fragile code.

Best Practices

  • Use reflection only when dynamic inspection is required.
  • Cache reflective lookups in frameworks to reduce overhead.
  • Always handle ClassNotFoundException, NoSuchMethodException, and IllegalAccessException.
  • Respect encapsulation—avoid unnecessary setAccessible(true).

Summary + Key Takeaways

  • Reflection enables runtime discovery of class structure, methods, fields, and constructors.
  • Frameworks like Spring, Hibernate, and JUnit rely on it heavily.
  • Use Class objects as the entry point to gather metadata.
  • While powerful, reflection comes with performance and security costs, so use it judiciously.

FAQ

  1. How do I get a class object at runtime?
    Use .class, Class.forName(), or obj.getClass().

  2. Can reflection access private members?
    Yes, with setAccessible(true), though it breaks encapsulation.

  3. Does reflection affect performance significantly?
    Reflection is slower than direct access but acceptable in framework-level code.

  4. What is the difference between getDeclaredMethods() and getMethods()?
    getDeclaredMethods() returns all methods declared in the class, while getMethods() includes inherited public methods.

  5. Can I use reflection to create objects?
    Yes, via Constructor.newInstance().

  6. How does reflection work with generics?
    Reflection uses type erasure but ParameterizedType can inspect generic type parameters.

  7. How do modules affect reflection in Java 9+?
    Strong encapsulation may block reflective access unless modules explicitly export packages.

  8. Is reflection used in Spring Boot?
    Yes, Spring Boot relies heavily on reflection for dependency injection and auto-configuration.

  9. Can I get annotation information via reflection?
    Yes, methods like getAnnotations() and isAnnotationPresent() provide this.

  10. What’s the safest way to use reflection in large projects?
    Limit usage to framework-level concerns, cache lookups, and document reflective access clearly.