Java Reflection is a powerful tool, but it’s also a performance and security minefield.

Let’s see MethodHandles in action. Imagine you want to call a private method on an arbitrary object.

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

class SecretAgent {
    private String revealSecret(String message) {
        return "Secret revealed: " + message;
    }
}

public class MethodHandleExample {
    public static void main(String[] args) throws Throwable {
        SecretAgent agent = new SecretAgent();

        // Get the lookup object for the SecretAgent class
        MethodHandles.Lookup lookup = MethodHandles.lookup();

        // Define the method type: takes a SecretAgent and a String, returns a String
        MethodType mt = MethodType.methodType(String.class, String.class);

        // Find the specific private method
        MethodHandle revealHandle = lookup.findVirtual(SecretAgent.class, "revealSecret", mt);

        // Invoke the method handle
        String result = (String) revealHandle.invoke(agent, "The eagle has landed.");

        System.out.println(result);
    }
}

Here, MethodHandles.lookup() gives us a Lookup object, which is essentially a "key" to access methods. We then define the MethodType (signature) of the method we’re looking for. Finally, findVirtual (for instance methods) locates the MethodHandle. invoke then executes it. Notice how we didn’t need to bypass any access checks explicitly; the Lookup object’s privilege (determined by where it’s created) grants us access.

The problem MethodHandles solve is that Reflection’s Method.invoke() is slow because it has to do a lot of dynamic checks every single time. It’s like having a security guard check your ID for every single room you enter in a building, even if you’ve been cleared for the whole building already. MethodHandles, on the other hand, are like getting a master key upfront. Once you have a MethodHandle, calling it is often as fast as a direct method call, because the JVM can optimize it much more aggressively.

The core of the mental model is the Lookup object and the MethodHandle itself. A Lookup object represents a "privilege scope" – it knows which class it was created from and what access rights that class has. This is crucial for security. You can’t just get a Lookup for any class and start poking around. You generally get a Lookup for the class you’re currently in. When you use a Lookup to find a method (like findVirtual, findStatic, findConstructor), the JVM checks if the privilege scope of the Lookup object allows access to that method. If it does, it returns a MethodHandle. A MethodHandle is a strongly-typed, low-level reference to a method (or constructor, or field). It’s not a Method object from Reflection; it’s a more direct, efficient representation. You can then invoke, invokeExact, or invokeWithArguments on this handle.

The secret sauce for performance is that MethodHandles can be inlined and optimized by the JIT compiler. Because they represent a specific, known method (once created), the JVM can often treat them like direct method calls in compiled code, eliminating the overhead of reflection’s dynamic dispatch and security checks on each invocation.

The trade-off is that MethodHandles are lower-level and more verbose to set up than Reflection. You have to explicitly define the MethodType and use specific find methods. Reflection, with getMethod and invoke, can feel simpler for basic use cases. However, for repeated calls, especially in performance-sensitive code, the initial setup cost of MethodHandles is quickly paid back by their superior invocation performance.

The part most people miss is how Lookup objects are constructed and what that implies. You can’t just create a Lookup out of thin air for any class. You typically use MethodHandles.lookup() (which creates a lookup for the calling class) or MethodHandles.privateLookupIn(Class<?> targetClass, MethodHandles.Lookup caller) (which allows a specific caller class to get a lookup for another class, provided certain security conditions are met). This mechanism is the bedrock of MethodHandles’ security model, preventing arbitrary code from accessing private members without proper authorization.

The next step is understanding how to link and transform MethodHandles.

Want structured learning?

Take the full Java course →