Skip to content

Instantly share code, notes, and snippets.

@carefree-ladka
Created February 26, 2026 10:16
Show Gist options
  • Select an option

  • Save carefree-ladka/e79a207ace3c0c81dacd578bf4b909a9 to your computer and use it in GitHub Desktop.

Select an option

Save carefree-ladka/e79a207ace3c0c81dacd578bf4b909a9 to your computer and use it in GitHub Desktop.
Java Reflection API

Java Reflection API

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.reflect package and works via the java.lang.Class object.


Table of Contents


What is Reflection?

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);

The Class Object

Every type in Java has a corresponding java.lang.Class object. It is the entry point for all reflection operations.

Obtaining a Class Object

// 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");

Basic Class Metadata

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());           // false

Inspecting Classes

Modifiers

import 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"

Implemented Interfaces

Class<?>[] interfaces = String.class.getInterfaces();
for (Class<?> iface : interfaces) {
    System.out.println(iface.getName());
}
// Outputs: java.io.Serializable, java.lang.Comparable, java.lang.CharSequence

Superclass Hierarchy

Class<?> clazz = Integer.class;
while (clazz != null) {
    System.out.println(clazz.getName());
    clazz = clazz.getSuperclass();
}
// java.lang.Integer -> java.lang.Number -> java.lang.Object -> null

Working with Fields

Key Methods

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

Reading and Writing Fields

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 static

Field Metadata

Field 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

Working with Methods

Key Methods

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

Invoking Methods

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.141592653589793

Method Metadata

Method 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())); // true

Generic Return Types

Method 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
}

Working with Constructors

Key Methods

Method Description
getConstructor(paramTypes...) Public constructor
getDeclaredConstructor(paramTypes...) Any constructor
getConstructors() All public constructors
getDeclaredConstructors() All constructors

Creating Instances

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();

Access Control & setAccessible

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, setAccessible may throw InaccessibleObjectException unless the module opens its packages. Use --add-opens JVM flags or module-info.java directives.

# JVM flag to open a package for deep reflection
--add-opens java.base/java.lang=ALL-UNNAMED

Annotations via Reflection

Reading Annotations

@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.


Arrays 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()); // int

Dynamic Proxies

java.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.


Use Cases

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

Performance Considerations

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

Optimization Tips

// ❌ 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 to Method.invoke(). They support inlining by the JIT compiler.


Security Considerations

  • 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

Best Practices

  • Cache reflection objects β€” Class, Method, Field, Constructor are expensive to look up
  • Prefer MethodHandles over Method.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

Quick Reference Cheat Sheet

// 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);

Further Reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment