Menu

Java Reflection API

Java Reflection API

Every example so far has assumed you know, at compile time, exactly which classes, fields, and methods you are working with. Reflection breaks that assumption deliberately: it lets a running program inspect, and even modify, the structure of classes and objects it only discovers at runtime.

Introduction to Reflection

Reflection is the ability of a program to examine and manipulate its own structure, classes, methods, fields, constructors, while it is running. Every loaded class in Java has a single, corresponding java.lang.Class object describing it, and reflection works by querying that Class object.

Class<?> clazz1 = String.class;                  // via the .class literal

Class<?> clazz2 = "hello".getClass();                // via an existing object

Class<?> clazz3 = Class.forName("java.lang.String"); // via a fully qualified name, by String

System.out.println(clazz1 == clazz2);   // true — same Class object, only one exists per type

Inspecting Classes at Runtime

Once you have a Class object, you can query nearly everything about the type it represents: its name, its superclass, the interfaces it implements, and every field, method, and constructor it declares.

class Employee {

private String name;

private double salary;

public Employee(String name, double salary) {

this.name = name;

this.salary = salary;

}

public void giveRaise(double amount) {

salary += amount;

}

}

Class<?> clazz = Employee.class;

System.out.println("Class name: " + clazz.getSimpleName());

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

for (Field field : clazz.getDeclaredFields()) {

System.out.println("Field: " + field.getName() + " (" + field.getType().getSimpleName() + ")");

}

for (Method method : clazz.getDeclaredMethods()) {

System.out.println("Method: " + method.getName());

}

Accessing Fields and Methods Dynamically

Beyond just listing fields and methods, reflection lets you read, write, and invoke them, by name, as plain strings, entirely bypassing the normal compile-time method call syntax.

Employee emp = new Employee("Asha", 50000);

Class<?> clazz = emp.getClass();

// Read a private field dynamically

Field salaryField = clazz.getDeclaredField("salary");

salaryField.setAccessible(true);   // bypass private access for this reflective call

double currentSalary = (double) salaryField.get(emp);

System.out.println("Current salary: " + currentSalary);

// Invoke a method dynamically, by name and parameter types

Method raiseMethod = clazz.getDeclaredMethod("giveRaise", double.class);

raiseMethod.invoke(emp, 5000.0);

System.out.println("Updated salary: " + salaryField.get(emp));   // 55000.0

Creating Objects Using Reflection

Reflection can also construct entirely new objects, choosing the class to instantiate based on information only available at runtime, such as a class name read from a configuration file.

Class<?> clazz = Class.forName("com.app.models.Employee");

Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, double.class);

Object newEmployee = constructor.newInstance("Ravi", 45000.0);

System.out.println(newEmployee.getClass().getSimpleName());   // Employee

This exact mechanism, reading a class name as a string and instantiating it via reflection, is how plugin systems, dependency injection frameworks, and many ORM (object-relational mapping) libraries dynamically wire together objects whose concrete types are only known from configuration, not hardcoded into the framework's own source.

Reflection and Encapsulation

setAccessible(true), used above to read a private field, deserves a moment of caution: it deliberately bypasses the access modifiers covered all the way back in Chapter 1 of the Intermediate tutorial. Reflection can, technically, read and even modify private fields from completely outside their class, something normal Java code can never do.

class SecretHolder {

private String secret = "classified";

}

SecretHolder holder = new SecretHolder();

Field secretField = SecretHolder.class.getDeclaredField("secret");

secretField.setAccessible(true);

System.out.println(secretField.get(holder));   // classified — encapsulation has been bypassed

Since Java 9's module system, this bypass is no longer unconditional: strongly encapsulated modules can refuse setAccessible(true) outright for fields outside their explicitly opened packages, throwing an InaccessibleObjectException instead. This was added specifically because unrestricted reflective access undermines the entire point of encapsulation if left completely unchecked.

Real-World Applications

Use Case

How Reflection Helps

Dependency injection frameworks (Spring, etc.)

Instantiate classes and inject dependencies based purely on annotations, with no hardcoded class list

Serialization libraries (Jackson, Gson)

Read and write an object's fields generically, without that object needing to implement any special interface

Unit testing frameworks (JUnit, Mockito)

Discover and invoke test methods automatically, and create mock objects on the fly

ORMs (Hibernate, JPA)

Map database columns to private object fields without requiring public getters and setters for every column

Plugin and extension systems

Load and instantiate classes whose names are only known from a configuration file at startup

Reflection Best Practices

  • Avoid reflection for ordinary application logic; reach for it only when you genuinely need to operate on types unknown until runtime, since normal method calls are both faster and far easier to read.
  • \Reflective calls are noticeably slower than direct method calls, since the JVM cannot apply many of its usual compile-time optimisations to them; avoid them in tight, performance-sensitive loops.
  • Be cautious with setAccessible(true); bypassing encapsulation should be a deliberate, narrow exception, not a habit, and it may simply be blocked by the module system in modern Java.
  • Wrap reflective calls in proper exception handling; many reflection methods throw checked exceptions like NoSuchMethodException or IllegalAccessException that must be handled explicitly.
  • Prefer higher-level alternatives where they exist, method handles (java.lang.invoke) and modern dependency injection frameworks often achieve the same goals with better performance and clearer code than raw reflection.

Did You Know?

Almost every popular Java framework you have likely heard of, Spring, Hibernate, JUnit, relies on reflection somewhere near its core. Reflection is, in a very real sense, the technology that makes Java's framework ecosystem possible, even though application developers using those frameworks rarely write reflective code themselves.