< Back

Java Method Handles: what, how and why?

Author

Mats Van Audenaeren

Date

19/10/2026

Share this article

It started like any other innocent evening. I was sitting at my desk, staring at IntelliJ, when I suddenly felt the urge to play Java Roulette: randomly pick a Java class, interface, or concept, read about it, experiment with it, and pray you end up a little bit smarter.

This time the wheel landed on Method Handles.

Now, before we dive into them, let me ask you a question: When you need to inspect a class or invoke a method dynamically at runtime, what API instantly comes to mind? For a lot of people the answer will be Java’s classic Reflection API. It’s trusty, it’s familiar, and it’s been around for ages.

But Reflection has a reputation for being slower than direct method invocation, especially when used repeatedly in hot code paths. It also doesn't give you the same strongly-typed method invocation model that Method Handles provide. Calling Reflection inside a tight loop can feel like going through full airport security every time you want to grab a drink from your own fridge.

For years, Reflection was all I used because... Well, I thought it was the only tool in the box for this kind of dynamic magic.

Then I met Method Handles.

What are Method Handles

Introduced back in Java 7, Method Handles are strongly-typed, directly executable references to underlying methods, constructors, or fields. You perform the security and lookup checks once, grab the handle, and fire away at superfast execution speed.

I read through the Javadoc, followed a few guides, and jumped straight into playtime to fidget with the API. Because nothing says "having fun on a weeknight" quite like writing custom under-the-hood code that probably shouldn't exist in production.

The basics

Under the hood, working with Method Handles always boils down to three main pieces: Lookup, Method Type and Method Handle. Let's dive into some code:

Image if we'd like to add an integer into an ArrayList of integers. Nothing too complicated but, we don't wanna use the conventional Java code for this example. We could do that through Reflection:

List<Integer> numericList = new ArrayList<>();

Method addMethod = List.class.getMethod("add", Object.class);

addMethod.invoke(numericList, 1);

Simple: We create a new ArrayList, fetch the method add and its parameter. The parameter of the add method is defined as a type Object. If we run this code, the integer 1 will be added to the ArrayList numericList.

We can also do this with Method Handles:

MethodHandles.Lookup lookup = MethodHandles.publicLookup();

MethodType methodType = MethodType.methodType(boolean.class, Object.class);

MethodHandle addMethodHandle = lookup.findVirtual(List.class, "add", methodType);

We first create a Lookup. This is your security checkpoint. Unlike Reflection, where access can sometimes be explicitly overridden depending on the API and context, a Lookup determines which classes and members the code is allowed to access based on the lookup's access privileges.

The MethodType is the method blueprint. It describes the exact return type and parameter types of the method you're hunting for. The add method of the List interface returns a boolean and its parameter is of the type Object. Method.methodType(boolean.class, Object.class) is literally saying: "Find me a method that returns a boolean and takes an Object".

The MethodHandle is a typed reference to the method we want to call. Once we have it, we can use the handle to invoke add without having to perform the method lookup again.

Now that the Method Handle is all set-up, we can invoke it:

addMethodHandle.invoke(numericList, i);

One important detail here is that Method Handles have two interesting invocation methods: invoke() and invokeExact(). invoke() is more flexible and can perform type adaptations when necessary. invokeExact() is much stricter: the type at the call site must exactly match the Method Handle's type.

So Method Handles give us a strongly-typed mechanism for describing and invoking methods, while still giving us some flexibility when we actually invoke them.

Transformations

Method Handles have many possibilities. One I found interesting was transformation. Method Handles are LEGO bricks. You can snap handles together, and pass outputs directly into inputs, to make a chain of actions.

Take a look at this Element enum setup. Every enum value has a String value. The enum class contains a method to retrieve an enum value based on the String value: getElementByValue(String upperCaseValue). Imagine a user passes in "helium" in lowercase. The enum value "HE" will never be found, because our input value is lower case:

public enum Element {
    H("HYDROGEN"),
    HE("HELIUM"),
    NE("NEON");
    public final String label;
        Element(String label) {
            this.label = label;
    }

    static Element getElementByValue(String upperCaseValue) {
        for (Element element : Element.values()) {
            if (element.label.equals(upperCaseValue)) {
                return element;
            }
        }
        return null;
    }
}

Instead of manually converting our String before invoking the handle, we can literally attach a toUpperCase transformer directly onto the method handle using MethodHandles.filterArguments():

We define our input value helium and retrieve the Lookup object:

String rawInput = "helium";
MethodHandles.Lookup lookup = MethodHandles.lookup();

Next, we need a MethodType and MethodHandle for our enum's getElementByValue method. Since it returns an Element and takes a single String, defining the MethodType is straightforward:

MethodType getElementByValueMethodType = MethodType.methodType(Element.class, String.class);

And because getElementByValue is a static method, we use findStatic to hunt it down, passing in the target class, the method name, and our freshly created MethodType:

MethodHandle getElementMethodHandle = lookup.findStatic(Element.class, "getElementByValue", getElementByValueMethodType);

Now we have our target MethodHandle for getElementByValue. But remember, it’s picky and strictly expects an uppercase String. So, we follow the exact same recipe to grab a handle for String.toUpperCase(). Since toUpperCase is an instance method on String, we use findVirtual instead of findStatic. Notice that its return type is String and it takes no parameters:

MethodType toUpperCaseMethodType = MethodType.methodType(String.class);

MethodHandle toUpperCaseMethodHandle = lookup.findVirtual(String.class, "toUpperCase", toUpperCaseMethodType);

What's left now is to combine them both. We can use the filterArguments method. We took our target handle getElementMethodHandle and told the JVM: "Hey, before passing argument 0 into this method, run it through toUpperCaseMethodHandle first." When we call invoke("helium"), the JVM automatically turns "helium" into "HELIUM" on the fly and passes the result straight into getElementByValue. No manual String conversion in our calling code. The conversion has become part of the Method Handle chain:

MethodHandle withFilterMethodHandle = MethodHandles.filterArguments(getElementMethodHandle, 0, toUpperCaseMethodHandle);

Element element = (Element) withFilterMethodHandle.invoke(rawInput);

Method Handle vs Reflection

Okay, I already know how to invoke methods at runtime using Reflection. Why on earth would I switch to Method Handles? They're clearly more complex, require more setup, and basically do the same thing. It's a fair point. If all you're building is a quick script to add an integer to a List, Reflection gets the job done perfectly well.

But Method Handles can have lower invocation overhead in some scenarios, particularly when a handle is looked up once and reused many times. So let's put our money where our mouth is with a simple experiment. We're going to add an integer to an ArrayList 100 million times. First we'll use Reflection, then we'll use a Method Handle and see who's faster.

First up, the reigning champion, Reflection:

public static void runWithReflection() {
    try {
        Method addMethod = List.class.getMethod("add", Object.class);
        for (int i = 0; i < 100_000_000; i++) {
            addMethod.invoke(numericList, i);
        }
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

Now, let's look at the Method Handle approach:

public static void runWithMethodHandler() {
    try {
        MethodHandles.Lookup lookup = MethodHandles.publicLookup();
        MethodType methodType = MethodType.methodType(boolean.class, Object.class);
        MethodHandle addMethodHandle = lookup.findVirtual(List.class, "add", methodType);
        for (int i = 0; i < 100_000_000; i++) {
            addMethodHandle.invoke(numericList, i);
        }
    } catch (Throwable e) {
        throw new RuntimeException(e);
    }
}

To prove the efficiency difference between the two we create a basic script that executes both methods and add a little bit extra code to monitor the time:

long startTimeReflection = System.nanoTime();
    runWithReflection();
    long endTimeReflection = System.nanoTime();
    double runTimeReflection = (endTimeReflection - startTimeReflection) / 1_000_000.0;
 

    long startTimeMethodHandle = System.nanoTime();
    runWithMethodHandler();
    long endTimeMethodHandle = System.nanoTime();
    double runTimeMethodHandle = (endTimeMethodHandle - startTimeMethodHandle) / 1_000_000.0;

    System.out.printf("Runtime with reflection: %f%n", runTimeReflection);
    System.out.printf("Runtime with method handler: %f%n", runTimeMethodHandle);

Let's run this and inspect the result:

Runtime with reflection: 4357,361400

Runtime with method handler: 3841,468500

Wait, only around a 500ms difference over 100 million iterations? Sure, Method Handles won in this particular experiment, but after writing all that extra code, you might be thinking: "Is that it?"

And that's actually the important part. This isn't a scientific benchmark proving that Method Handles are always faster than Reflection. JVM performance depends on a lot of things: Java version, JVM implementation, warm-up, hardware, invocation pattern, ...

For a proper performance comparison, you'd want to use something like Java Microbenchmark Harness (JMH) and run multiple iterations with proper JVM warm-up. But even with this simple experiment, we can see that Method Handles can perform competitively with Reflection, while also giving us a composable and strongly-typed mechanism for dynamic invocation.

The profiler can give us another interesting look at what's happening under the hood:

Looking at the two stacks side by side gives us a better idea of what happens during the invocation. Reflection has additional machinery involved in turning a reflective invocation into an actual method call. The Method Handle invocation can be optimized by the JVM and may end up with a much more direct execution path.

Where to use Method Handles

So, after all this talk about lookups, method types, transformations, and JVM magic, the obvious question remains: when should you actually use Method Handles? Because let's be honest, if you're writing a normal Java application and you know at compile time which method you want to call, you probably shouldn't. There is absolutely nothing wrong with:

That's actually pretty close to the little experiment I started playing with. We can use a Method Handle to dynamically create an object:

private static <T> T createObject(Class<T> classType) {
    MethodType constructorMethodType = MethodType.methodType(void.class);
    try {
        MethodHandle constructorMethodHandle = lookup.findConstructor(classType, constructorMethodType);
        return (T) constructorMethodHandle.invoke();
    } catch (Throwable e) {
        throw new RuntimeException(e);
    }
}

And then dynamically find the appropriate setter and invoke it:

private static <T> void addDataToObject(T object, Map<String, String> jsonKeyValueMap) {
    MethodType setterMethodType = MethodType.methodType(void.class, String.class);
 

    for (Map.Entry<String, String> entry : jsonKeyValueMap.entrySet()) {
        String field = entry.getKey();
        String value = entry.getValue();
        field = field.substring(0, 1).toUpperCase().concat(field.substring(1));
        String setterMethodName = "set" + field;


        try {
            MethodHandle setterMethodeHandle = lookup.findVirtual(object.getClass(), setterMethodName, setterMethodType);
            setterMethodeHandle.invoke((T) object, value);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}

Suddenly, we have something that can take a class, create an instance of it, and populate it without knowing the class's setters at compile time. And this is where I think Method Handles make the most sense: when you're building something that itself needs to work dynamically.

What I learned

Method Handles are more complicated than Reflection. The API is not exactly what I'd call beginner-friendly, and there are quite a few concepts to understand before everything starts clicking. You also need to be careful with MethodType, access rules, invoke() versus invokeExact(), and all those lovely exceptions that you'll meet along the way. So I wouldn't replace every Method.invoke() in your codebase with a Method Handle just because this article told you it's faster.

If you need to dynamically call something once or twice, Reflection is probably perfectly fine. If you're building infrastructure that dynamically discovers and repeatedly invokes methods, however, Method Handles are definitely worth having in your toolbox.

Not bad for a random Tuesday evening playing Java Roulette.