Skip to content
Java reflection 7 min read

Reflection API

Java’s Reflection API gives your code the ability to examine itself — to look up class names, list methods and fields, invoke methods, and even tweak private members, all at runtime without knowing the types at compile time. It’s the engine behind dependency injection frameworks, testing tools, serialization libraries, and IDE auto-complete.

What Is Reflection?

Normally, Java is statically typed: you write String s = "hello" and the compiler knows everything about String before the program runs. Reflection breaks through that wall. Using the java.lang.reflect package together with the java.lang.Class object, your program can:

  • Discover the name, superclass, and interfaces of any class
  • List every constructor, method, and field — including private ones
  • Invoke methods or access fields by name at runtime
  • Create new instances without calling new directly

This makes it incredibly powerful — and, if misused, a source of fragile, hard-to-debug code. Use it deliberately.

Note: Reflection is part of the java.lang.reflect package. You don’t need a separate import for Class itself, since it lives in java.lang.

Getting a Class Object

Everything in Reflection starts with a Class<?> object. There are three ways to obtain one:

// 1. From a type literal (compile-time)
Class<String> c1 = String.class;

// 2. From an existing instance
String text = "hello";
Class<?> c2 = text.getClass();

// 3. By fully-qualified name (runtime string — most dynamic)
Class<?> c3 = Class.forName("java.util.ArrayList");

System.out.println(c1.getName()); // java.lang.String
System.out.println(c2.getSimpleName()); // String
System.out.println(c3.getSimpleName()); // ArrayList

Output:

java.lang.String
String
ArrayList

Class.forName() is especially useful when the class name comes from configuration, a database, or user input.

Inspecting Class Metadata

Once you have a Class<?>, you can interrogate it for all kinds of structural information.

import java.lang.reflect.*;

public class MetadataDemo {
    public static void main(String[] args) {
        Class<?> cls = java.util.LinkedList.class;

        System.out.println("Name      : " + cls.getName());
        System.out.println("Superclass: " + cls.getSuperclass().getSimpleName());

        System.out.print("Interfaces: ");
        for (Class<?> iface : cls.getInterfaces()) {
            System.out.print(iface.getSimpleName() + " ");
        }
        System.out.println();

        System.out.println("Is abstract? " + Modifier.isAbstract(cls.getModifiers()));
    }
}

Output:

Name      : java.util.LinkedList
Superclass: AbstractSequentialList
Interfaces: List Deque Cloneable Serializable 
Is abstract? false

The Modifier utility class converts the raw int returned by getModifiers() into readable boolean flags like isPublic(), isStatic(), isFinal(), etc.

Listing Methods and Fields

import java.lang.reflect.*;

public class MemberInspector {
    public static void main(String[] args) {
        Class<?> cls = String.class;

        // getDeclaredMethods() — only this class, all access levels
        // getMethods()         — public methods from this class + superclasses
        Method[] methods = cls.getDeclaredMethods();
        System.out.println("Total declared methods in String: " + methods.length);

        // Print a few fields
        Field[] fields = cls.getDeclaredFields();
        System.out.println("Declared fields in String:");
        for (Field f : fields) {
            System.out.println("  " + Modifier.toString(f.getModifiers())
                               + " " + f.getType().getSimpleName()
                               + " " + f.getName());
        }
    }
}

Output (Java 21, abbreviated):

Total declared methods in String: 86
Declared fields in String:
  private final byte[] value
  private final byte coder
  private int hash
  ...

Tip: Prefer getDeclaredXxx() when you want everything on the class itself. Use getXxx() (without “Declared”) when you want only public, inherited members.

Invoking Methods Dynamically

The real power shows when you invoke a method by name at runtime:

import java.lang.reflect.*;

public class DynamicInvoke {
    public static void main(String[] args) throws Exception {
        String target = "  hello world  ";

        // Look up the trim() method — no parameters, so empty Class array
        Method trim = String.class.getMethod("trim");
        String result = (String) trim.invoke(target);
        System.out.println("'" + result + "'");

        // Method with a parameter
        Method toUpperCase = String.class.getMethod("substring", int.class);
        String sub = (String) toUpperCase.invoke(target, 3);
        System.out.println("'" + sub + "'");
    }
}

Output:

'hello world'
'llo world  '

method.invoke(instance, args...) calls the method on instance with the given arguments. For static methods, pass null as the first argument.

Creating Instances Without new

Reflection lets you instantiate a class when you only know its name as a string — a pattern frameworks like Spring use extensively. See Creating Objects Reflectively for the full story.

import java.lang.reflect.*;

public class ReflectiveNew {
    public static void main(String[] args) throws Exception {
        // Requires a no-arg constructor
        Class<?> cls = Class.forName("java.util.ArrayList");
        Object list = cls.getDeclaredConstructor().newInstance();
        System.out.println(list.getClass().getSimpleName()); // ArrayList

        // Constructor with arguments
        Constructor<?> con = StringBuilder.class.getConstructor(String.class);
        Object sb = con.newInstance("start-");
        System.out.println(sb); // start-
    }
}

Output:

ArrayList
start-

Note: The legacy Class.newInstance() method (removed in Java 17 as best practice) has been replaced by getDeclaredConstructor().newInstance(), which properly propagates checked exceptions.

Accessing Private Members

Reflection can bypass access control with setAccessible(true). This is explored in depth on Accessing Private Members, but here is a quick example:

import java.lang.reflect.*;

public class PrivateAccess {
    private String secret = "classified";
}

class Demo {
    public static void main(String[] args) throws Exception {
        PrivateAccess obj = new PrivateAccess();
        Field f = PrivateAccess.class.getDeclaredField("secret");
        f.setAccessible(true); // override access check
        System.out.println(f.get(obj)); // classified
        f.set(obj, "revealed");
        System.out.println(f.get(obj)); // revealed
    }
}

Output:

classified
revealed

Warning: setAccessible(true) bypasses encapsulation. Since Java 9, the module system may block it with an InaccessibleObjectException unless the module explicitly opens the package. Never use this in production code on JDK internals — it can break with any JDK update.

Checking Annotations at Runtime

Reflection is also how annotation-processing libraries read metadata at runtime. Only annotations marked @Retention(RetentionPolicy.RUNTIME) are visible through reflection.

import java.lang.annotation.*;
import java.lang.reflect.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Log {}

class Service {
    @Log
    public void process() { System.out.println("processing"); }

    public void skip() {}
}

public class AnnotationCheck {
    public static void main(String[] args) throws Exception {
        for (Method m : Service.class.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Log.class)) {
                System.out.println("@Log found on: " + m.getName());
                m.invoke(new Service());
            }
        }
    }
}

Output:

@Log found on: process
processing

This pattern is exactly how JUnit discovers @Test methods and how Spring finds @RequestMapping handlers.

Under the Hood

How Class Objects Work

Every class loaded by the JVM has exactly one Class<T> instance in the method area (part of the heap since Java 8’s removal of PermGen). That object holds a reference to the class’s bytecode metadata: the constant pool, field descriptors, method descriptors, and access flags. Reflection APIs read directly from this metadata.

Performance Cost

Reflective calls are significantly slower than direct calls:

OperationApprox. cost vs direct call
method.invoke() (first call)~10–20× slower
method.invoke() (after JIT warms up)~2–5× slower
Field get/set~3–6× slower

The JVM generates accessor code on demand and caches it after a threshold (controlled by sun.reflect.inflationThreshold, default 15). After that threshold, the JIT can optimize heavily, but the overhead never fully disappears because of argument boxing, varargs arrays, and access checks. In tight loops, prefer direct APIs or MethodHandle (available since Java 7) which compiles down to near-direct-call performance.

MethodHandle — The Modern Alternative

java.lang.invoke.MethodHandle (introduced in Java 7) offers a JIT-friendly, type-safe way to do many of the same things as reflection without the overhead. Lambdas and default methods internally use MethodHandles via invokedynamic bytecode. For new code where performance matters, prefer MethodHandle over Method.invoke().

Module System Restrictions (Java 9+)

The Java 9 module system added strong encapsulation. Calling setAccessible(true) on a member in an unexported or unopened package throws InaccessibleObjectException at runtime. To allow deep reflection, the module must declare opens <package> in its module-info.java. This is why you sometimes see --add-opens flags on the command line when running older frameworks on Java 11+.

In This Section

  • Creating Objects Reflectively — Use Constructor.newInstance() to instantiate classes at runtime from a string class name.
  • javap Tool — Disassemble .class files to inspect bytecode, method signatures, and the constant pool from the command line.
  • Accessing Private Members — Use setAccessible(true) to read and write private fields and invoke private methods, with module-system caveats.

Common Use Cases

  • Dependency injection — Spring, Guice, and CDI scan the classpath, read annotations, and wire beans together without compile-time knowledge of implementations.
  • Test frameworks — JUnit and TestNG discover and invoke @Test methods dynamically.
  • Serialization — Jackson, Gson, and Java’s own ObjectInputStream read field values to serialize/deserialize objects.
  • IDE tooling — Auto-complete, debuggers, and profilers interrogate class structure at runtime.
  • ORM frameworks — Hibernate maps Java fields to database columns by reading field names and types reflectively.

Tip: If you catch yourself writing reflection in application code, ask whether an interface or a design pattern like Strategy would solve the problem more cleanly. Reflection is a powerful escape hatch, not a first resort.

  • Annotations — Understanding @Retention and @Target is essential for writing frameworks that use reflection to read metadata.
  • Interface — Many reflection patterns can be replaced with polymorphism; understand the trade-offs.
  • Java 9: Modules (JPMS) — Module encapsulation directly affects what reflection can access; learn the opens directive.
  • Class Loaders & Class Loading — Reflection relies on the class loader hierarchy; Class.forName() uses the calling class’s loader by default.
  • Creating Objects Reflectively — The next step: instantiate any class dynamically using Constructor.newInstance().
  • Design Patterns — Patterns like Factory and Proxy are often implemented with reflection under the hood.
Last updated June 13, 2026
Was this helpful?