Java Reflection Explained: Classes, Methods, Fields, and Framework Use

Learn Java Reflection with Class, Method, Field, and Constructor APIs, how Spring and JPA use it, performance costs, and safe real-world use.

Last reviewed: August 2026

·Java 8Java 11Java 17Java 21
✅
Quick Understanding: Java Reflection lets code inspect or invoke classes, methods, fields, and constructors at runtime. Frameworks such as Spring, JPA, Jackson, and JUnit use it heavily; application code should use it carefully because it trades compile-time safety and speed for runtime flexibility.

🔍 What Is It?

Core Reflection Classes:

  • →Class<T> — represents a class; get with obj.getClass() or MyClass.class
  • →Method — represents a method; invoke with method.invoke(target, args)
  • →Field — represents a field; get/set with field.get(obj) / field.set(obj, value)
  • →Constructor<T> — represents a constructor; create with constructor.newInstance(args)
  • →Annotation — represents an annotation; read with element.getAnnotation(type)

Getting Class Information:

Class<?> clazz = String.class;
clazz.getName()                // "java.lang.String"
clazz.getSimpleName()          // "String"
clazz.getDeclaredFields()      // all fields (including private)
clazz.getFields()              // only public fields (including inherited)
clazz.getDeclaredMethods()     // all methods (including private)
clazz.getMethods()             // only public methods (including inherited)
clazz.getDeclaredAnnotations() // annotations on this class
clazz.getSuperclass()          // parent class
clazz.getInterfaces()          // implemented interfaces

Java 9+ Module System:

Reflection on non-exported packages requires: --add-opens module/package=ALL-UNNAMED or explicit module configuration. Frameworks are adapting to this.

🎯 Why Do We Need It?

Reflection enables generic frameworks:

  • →Spring DI — scans classpath for @Component, injects @Autowired fields
  • →JPA/Hibernate — maps @Entity fields to database columns
  • →Jackson — serializes/deserializes by reading field names and types
  • →JUnit — finds @Test methods and invokes them
  • →Lombok — generates code based on annotations (at compile time, not reflection)

Without reflection, these frameworks would require explicit configuration for every class — defeating their purpose.

🏗️ What Happens in Real Projects?

As an application developer, you use reflection indirectly:

  • →When Spring injects @Autowired dependencies
  • →When Jackson serializes your @JsonProperty fields
  • →When JPA maps your @Column fields to SQL
  • →When you write custom annotation processors

You write reflection code directly for: custom frameworks, testing utilities, serialization libraries, and AOP implementations.

⚙️ How It Works

Reflection Performance:

Reflection is slower than direct code because:

  • →Method lookup requires searching the class hierarchy
  • →Type checking is done at runtime, not compile time
  • →JIT optimization is limited for reflective calls

Mitigation:

  • →Cache Method/Field objects after first lookup
  • →Use MethodHandles (Java 7+) — faster than reflection for repeated calls
  • →Use invokedynamic — how lambdas are implemented

Access Control:

field.setAccessible(true) bypasses private/protected access modifiers. This is how Spring injects @Autowired private fields. In Java 9+, this requires module system permissions.

💻 Example

Reflection in Practice — How reflection powers framework features and how to use it safely.

java
// Inspect a class at runtime
Class<?> clazz = Employee.class;

// Get all declared fields (including private)
for (Field field : clazz.getDeclaredFields()) {
    System.out.printf("Field: %s %s%n", 
        field.getType().getSimpleName(), field.getName());
}

// Read a private field value
Employee emp = new Employee(1L, "Alice", "Engineering");
Field nameField = Employee.class.getDeclaredField("name");
nameField.setAccessible(true);  // bypass private access
String name = (String) nameField.get(emp);
System.out.println(name);  // Alice

// Invoke a private method
Method method = Employee.class.getDeclaredMethod("calculateBonus", BigDecimal.class);
method.setAccessible(true);
BigDecimal bonus = (BigDecimal) method.invoke(emp, new BigDecimal("100000"));

// Create instance dynamically
Class<?> serviceClass = Class.forName("com.example.OrderService");
Constructor<?> constructor = serviceClass.getDeclaredConstructor();
Object service = constructor.newInstance();

// Read annotations
for (Method m : clazz.getDeclaredMethods()) {
    if (m.isAnnotationPresent(Deprecated.class)) {
        System.out.println("Deprecated method: " + m.getName());
    }
}

🤔 Common Confusions

❓ Is reflection slow? Should I avoid it?

Reflection is slower than direct code — typically 10-100x for method invocation. However, this matters only in hot paths (called millions of times per second). Framework initialization (Spring startup) uses reflection but only runs once. Jackson serializes millions of objects but caches the reflection metadata. In application code, reflection is fine if you cache Method/Field objects. Never use reflection in tight loops.

❓ What is the difference between getFields() and getDeclaredFields()?

getDeclaredFields() returns all fields declared in this class (including private, protected, package-private) but NOT inherited fields. getFields() returns all public fields of this class AND all public inherited fields. For accessing private fields, use getDeclaredFields() + setAccessible(true). For a complete picture of all fields including inherited, iterate up the class hierarchy using getSuperclass().

🚨 Common Production Issues

Performance in tight loops — calling method.invoke() in a loop processing millions of records is slow. Cache Method objects; consider MethodHandles for better JIT optimization.

Java 9+ module system breaking reflection — accessing non-exported packages throws InaccessibleObjectException. Requires --add-opens or module configuration.

Security manager restrictions — in some environments (application servers, security-hardened JVMs), setAccessible(true) may be denied.

✅ Best Practices

Cache Method, Field, and Constructor objects — lookup is expensive; invocation is cheaper.

Use MethodHandles instead of Method.invoke() for performance-critical reflection.

Prefer annotation processors (compile-time) over runtime reflection when possible.

Do not use reflection in application business logic — use it only for framework/infrastructure code.

Handle InaccessibleObjectException gracefully for Java 9+ module compatibility.

🏛️ Architect's Note

Senior Architect's Perspective

Modern Java frameworks are moving away from runtime reflection toward compile-time code generation (Lombok, MapStruct, Quarkus, Micronaut). Compile-time approaches: faster startup (no classpath scanning), better GraalVM Native Image compatibility, and better IDE support. Spring Boot 3+ supports AOT (Ahead-Of-Time) compilation which pre-processes reflection at build time for faster startup and native image support.

💬 Frequently Asked Questions

What is the difference between Reflection and MethodHandles?

MethodHandles (java.lang.invoke.MethodHandle) is a more modern, faster alternative to reflection for method invocation. MethodHandles are directly invokable and the JIT can inline them (unlike Method.invoke() which is harder to optimize). Use MethodHandles when you need high-performance dynamic dispatch. The Lookup API also respects module system access controls more cleanly than setAccessible().

How do annotation processors differ from reflection?

Annotation processors run at compile time (javac plugin) and generate source code. Reflection runs at runtime and inspects existing code. Annotation processors are faster (no runtime overhead), work with GraalVM Native Image, and produce code that IDEs understand. Lombok, MapStruct, and Micronaut use annotation processors. Spring (traditionally) uses runtime reflection, but Spring Boot 3+ AOT mode shifts to compile-time processing.

🎯 Key Takeaways

1Reflection inspects and manipulates classes, methods, and fields at runtime
2getDeclaredFields(): all fields in this class; getFields(): all public fields including inherited
3setAccessible(true) bypasses access modifiers — use carefully, Java 9+ has restrictions
4Cache Method/Field objects — lookup is expensive, invocation is cheaper
5Frameworks (Spring, Jackson, JPA) use reflection internally — you rarely write it directly
6Prefer compile-time annotation processing over runtime reflection for new frameworks

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages