Java Reflection is a powerful feature of the Java language that allows programs to inspect, examine, and modify their own structure and behavior at runtime β without knowing the types or members at compile time.
Reflection is part of the
java.lang.reflectpackage and works via thejava.lang.Classobject.
- What is Reflection?
- The Class Object
- Inspecting Classes
- Working with Fields
- Working with Methods
- Working with Constructors
- Access Control & setAccessible
- Annotations via Reflection
- Arrays via Reflection
- Dynamic Proxies
- Use Cases
- Performance Considerations
- Security Considerations
- Best Practices
Reflection enables:
- Inspecting class metadata (name, superclass, interfaces, modifiers)
- Listing and accessing fields, methods, and constructors
- Creating instances without
new - Invoking methods dynamically
- Reading/writing field values at runtime
// Without reflection
MyClass obj = new MyClass();
obj.doSomething();
// With reflection
Class<?> clazz = Class.forName("com.example.MyClass");
Object obj = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod("doSomething");
method.invoke(obj);Every type in Java has a corresponding java.lang.Class object. It is the entry point for all reflection operations.
// 1. Using .class literal (compile-time)
Class<String> c1 = String.class;
// 2. Using getClass() on an instance
String str = "hello";
Class<?> c2 = str.getClass();
// 3. Using Class.forName() (runtime, may throw ClassNotFoundException)
Class<?> c3 = Class.forName("java.lang.String");
// 4. From a ClassLoader
Class<?> c4 = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");Class<?> clazz = String.class;
System.out.println(clazz.getName()); // java.lang.String
System.out.println(clazz.getSimpleName()); // String
System.out.println(clazz.getCanonicalName()); // java.lang.String
System.out.println(clazz.getPackageName()); // java.lang
System.out.println(clazz.getSuperclass()); // class java.lang.Object
System.out.println(clazz.isInterface()); // false
System.out.println(clazz.isEnum()); // false
System.out.println(clazz.isAnnotation()); // false
System.out.println(clazz.isPrimitive()); // false
System.out.println(clazz.isArray()); // falseimport java.lang.reflect.Modifier;
Class<?> clazz = MyClass.class;
int mods = clazz.getModifiers();
System.out.println(Modifier.isPublic(mods)); // true/false
System.out.println(Modifier.isAbstract(mods)); // true/false
System.out.println(Modifier.isFinal(mods)); // true/false
System.out.println(Modifier.toString(mods)); // "public final"Class<?>[] interfaces = String.class.getInterfaces();
for (Class<?> iface : interfaces) {
System.out.println(iface.getName());
}
// Outputs: java.io.Serializable, java.lang.Comparable, java.lang.CharSequenceClass<?> clazz = Integer.class;
while (clazz != null) {
System.out.println(clazz.getName());
clazz = clazz.getSuperclass();
}
// java.lang.Integer -> java.lang.Number -> java.lang.Object -> null| Method | Description |
|---|---|
getField(name) |
Public field (including inherited) |
getDeclaredField(name) |
Field declared in this class (any access) |
getFields() |
All public fields (including inherited) |
getDeclaredFields() |
All fields declared in this class |
public class Person {
public String name = "Alice";
private int age = 30;
}
// --- Reading a public field ---
Person p = new Person();
Field nameField = Person.class.getField("name");
String name = (String) nameField.get(p);
System.out.println(name); // Alice
// --- Writing a public field ---
nameField.set(p, "Bob");
// --- Reading a private field ---
Field ageField = Person.class.getDeclaredField("age");
ageField.setAccessible(true); // bypass access check
int age = (int) ageField.get(p);
System.out.println(age); // 30
// --- Writing a private field ---
ageField.set(p, 25);
// --- Reading a static field ---
Field staticField = MyClass.class.getDeclaredField("STATIC_VALUE");
staticField.setAccessible(true);
Object value = staticField.get(null); // null for staticField f = Person.class.getDeclaredField("age");
System.out.println(f.getName()); // age
System.out.println(f.getType()); // int
System.out.println(f.getGenericType()); // int
System.out.println(Modifier.isPrivate(f.getModifiers())); // true| Method | Description |
|---|---|
getMethod(name, paramTypes...) |
Public method (including inherited) |
getDeclaredMethod(name, paramTypes...) |
Method declared in this class |
getMethods() |
All public methods |
getDeclaredMethods() |
All methods declared in this class |
public class Calculator {
public int add(int a, int b) { return a + b; }
private String secret() { return "hidden"; }
public static double pi() { return Math.PI; }
}
Calculator calc = new Calculator();
// --- Invoking a public method ---
Method addMethod = Calculator.class.getMethod("add", int.class, int.class);
int result = (int) addMethod.invoke(calc, 3, 4);
System.out.println(result); // 7
// --- Invoking a private method ---
Method secretMethod = Calculator.class.getDeclaredMethod("secret");
secretMethod.setAccessible(true);
String secret = (String) secretMethod.invoke(calc);
System.out.println(secret); // hidden
// --- Invoking a static method ---
Method piMethod = Calculator.class.getMethod("pi");
double pi = (double) piMethod.invoke(null); // null for static
System.out.println(pi); // 3.141592653589793Method m = Calculator.class.getMethod("add", int.class, int.class);
System.out.println(m.getName()); // add
System.out.println(m.getReturnType()); // int
System.out.println(Arrays.toString(m.getParameterTypes())); // [int, int]
System.out.println(Arrays.toString(m.getExceptionTypes()));
System.out.println(Modifier.isPublic(m.getModifiers())); // trueMethod m = MyClass.class.getMethod("getList");
Type returnType = m.getGenericReturnType();
if (returnType instanceof ParameterizedType pt) {
Type[] typeArgs = pt.getActualTypeArguments();
System.out.println(typeArgs[0]); // e.g., class java.lang.String
}| Method | Description |
|---|---|
getConstructor(paramTypes...) |
Public constructor |
getDeclaredConstructor(paramTypes...) |
Any constructor |
getConstructors() |
All public constructors |
getDeclaredConstructors() |
All constructors |
public class Dog {
private String name;
public Dog() { this.name = "Unknown"; }
public Dog(String name) { this.name = name; }
}
// --- No-arg constructor ---
Constructor<Dog> noArg = Dog.class.getConstructor();
Dog d1 = noArg.newInstance();
// --- Parameterized constructor ---
Constructor<Dog> paramCtor = Dog.class.getConstructor(String.class);
Dog d2 = paramCtor.newInstance("Rex");
// --- Private constructor (e.g., Singleton) ---
Constructor<Dog> privateCtor = Dog.class.getDeclaredConstructor(String.class);
privateCtor.setAccessible(true);
Dog d3 = privateCtor.newInstance("Shadow");
// --- Shortcut for no-arg public constructor ---
Dog d4 = Dog.class.getDeclaredConstructor().newInstance();By default, reflection respects Java access modifiers. Call setAccessible(true) to bypass them:
field.setAccessible(true);
method.setAccessible(true);
constructor.setAccessible(true);
β οΈ Java 9+ Module System: In modular applications,setAccessiblemay throwInaccessibleObjectExceptionunless the module opens its packages. Use--add-opensJVM flags ormodule-info.javadirectives.
# JVM flag to open a package for deep reflection
--add-opens java.base/java.lang=ALL-UNNAMED@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value() default "default";
int priority() default 1;
}
// --- Check if annotation present ---
Method m = MyClass.class.getMethod("myMethod");
if (m.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation ann = m.getAnnotation(MyAnnotation.class);
System.out.println(ann.value());
System.out.println(ann.priority());
}
// --- Get all annotations on a class ---
Annotation[] annotations = MyClass.class.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a.annotationType().getName());
}
// --- Declared vs inherited ---
clazz.getDeclaredAnnotations(); // only on this element
clazz.getAnnotations(); // including inheritedπ Only annotations with
@Retention(RetentionPolicy.RUNTIME)are visible via reflection.
import java.lang.reflect.Array;
// --- Create array dynamically ---
int[] arr = (int[]) Array.newInstance(int.class, 5);
Array.set(arr, 0, 42);
System.out.println(Array.get(arr, 0)); // 42
System.out.println(Array.getLength(arr)); // 5
// --- Multi-dimensional ---
int[][] matrix = (int[][]) Array.newInstance(int.class, 3, 3);
// --- Check if array type ---
System.out.println(int[].class.isArray()); // true
System.out.println(int[].class.getComponentType()); // intjava.lang.reflect.Proxy creates a runtime implementation of one or more interfaces.
import java.lang.reflect.*;
public interface Greeter {
String greet(String name);
}
// --- Create a proxy ---
Greeter proxy = (Greeter) Proxy.newProxyInstance(
Greeter.class.getClassLoader(),
new Class<?>[]{ Greeter.class },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before: " + method.getName());
String result = "Hello, " + args[0] + "!";
System.out.println("After: " + method.getName());
return result;
}
}
);
System.out.println(proxy.greet("World")); // Hello, World!Dynamic proxies are widely used in frameworks for AOP, logging, lazy loading, and transaction management.
Reflection powers many major Java frameworks and tools:
| Use Case | Examples |
|---|---|
| Dependency Injection | Spring, Guice |
| ORM / Data Mapping | Hibernate, JPA, MyBatis |
| Serialization | Jackson, Gson, Kryo |
| Testing | JUnit, Mockito |
| Web Frameworks | Spring MVC, JAX-RS |
| AOP / Proxies | Spring AOP, AspectJ |
| Plugin Systems | OSGI, Service Loaders |
| IDEs / Debuggers | IntelliJ IDEA, Eclipse |
Reflection is slower than direct code due to:
- Security checks on every invocation (unless cached)
- JVM's inability to apply certain JIT optimizations
- Boxing/unboxing of primitives
// β Slow: Lookup method on every call
for (int i = 0; i < 10000; i++) {
Method m = MyClass.class.getMethod("compute");
m.invoke(obj);
}
// β
Fast: Cache Method/Field/Constructor objects
Method m = MyClass.class.getMethod("compute");
m.setAccessible(true); // cache this too
for (int i = 0; i < 10000; i++) {
m.invoke(obj);
}// β
Use MethodHandles for better performance (Java 7+)
import java.lang.invoke.*;
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findVirtual(MyClass.class, "compute",
MethodType.methodType(int.class));
int result = (int) mh.invoke(obj);MethodHandles (
java.lang.invoke) are the modern, high-performance alternative toMethod.invoke(). They support inlining by the JIT compiler.
setAccessible(true)can break encapsulation and expose sensitive data- In Java 9+, the module system restricts reflective access to enforce encapsulation
- Reflection can be disabled or restricted by a
SecurityManager(deprecated in Java 17, removed in Java 24) - Avoid using reflection on untrusted input (e.g.,
Class.forName(userInput)) β this can lead to class injection vulnerabilities
// β Dangerous: user-controlled class name
String className = request.getParameter("class");
Class<?> clazz = Class.forName(className); // NEVER do this
// β
Safe: allowlist-based resolution
Map<String, Class<?>> allowed = Map.of(
"Dog", Dog.class,
"Cat", Cat.class
);
Class<?> clazz = allowed.get(className); // safe lookup- Cache reflection objects β
Class,Method,Field,Constructorare expensive to look up - Prefer
MethodHandlesoverMethod.invoke()for performance-critical code - Minimize
setAccessible(true)β prefer proper API design instead of reflective hacks - Use generics carefully β generic type info is erased at runtime; use
getGenericType()for parameterized types - Check before casting β always check
isInstance()before casting a reflectively obtained object - Document reflective usage β reflection is implicit; add comments explaining why it's necessary
- Avoid in hot paths β reflection is best suited for initialization, configuration, and framework code, not tight inner loops
// Get class
Class<?> c = MyClass.class;
Class<?> c = obj.getClass();
Class<?> c = Class.forName("com.example.MyClass");
// Fields
Field f = c.getDeclaredField("fieldName");
f.setAccessible(true);
Object val = f.get(obj); // read
f.set(obj, newValue); // write
Object val = f.get(null); // static field
// Methods
Method m = c.getDeclaredMethod("methodName", ParamType.class);
m.setAccessible(true);
Object result = m.invoke(obj, args); // instance
Object result = m.invoke(null, args); // static
// Constructors
Constructor<?> ctor = c.getDeclaredConstructor(ParamType.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(args);
// Annotations
boolean has = m.isAnnotationPresent(MyAnnot.class);
MyAnnot ann = m.getAnnotation(MyAnnot.class);
// Array
Object arr = Array.newInstance(ComponentType.class, length);
Array.set(arr, index, value);
Object val = Array.get(arr, index);