Contents
- Lookup and Finding Methods
- Invoking: invokeExact vs invoke
- Method Handle Transformations
- Unreflect: From java.lang.reflect
- Practical Use Cases
Method handles are obtained from a MethodHandles.Lookup object, which carries the access rights of the class where it was created. Access control is checked once at lookup time, not on every invocation.
import java.lang.invoke.*;
// Lookup has the access rights of the calling class
MethodHandles.Lookup lookup = MethodHandles.lookup();
// findVirtual — instance methods (dispatch based on receiver type)
MethodHandle toUpperCase = lookup.findVirtual(
String.class, "toUpperCase", MethodType.methodType(String.class));
// MethodType: (return type, param types...)
// For instance methods, receiver type is the first argument at invocation
// findStatic — static methods
MethodHandle parseInt = lookup.findStatic(
Integer.class, "parseInt", MethodType.methodType(int.class, String.class));
// findConstructor — constructors
MethodHandle newStringBuilder = lookup.findConstructor(
StringBuilder.class, MethodType.methodType(void.class, String.class));
// findGetter / findSetter — field access (same as get/set field in reflection)
MethodHandle nameGetter = lookup.findGetter(Person.class, "name", String.class);
MethodHandle nameSetter = lookup.findSetter(Person.class, "name", String.class);
// findSpecial — super method calls (like invokespecial bytecode)
// Only accessible from the class doing the call
MethodHandle superToString = lookup.findSpecial(
Object.class, "toString",
MethodType.methodType(String.class), MyClass.class);
There are two invocation styles with very different type-checking semantics:
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle toUpperCase = lookup.findVirtual(
String.class, "toUpperCase", MethodType.methodType(String.class));
// invokeExact — types must match EXACTLY (fastest, JIT-inlineable)
String result = (String) toUpperCase.invokeExact("hello");
// ^ Works: receiver is String, return cast to String
// invokeExact will throw WrongMethodTypeException if types don't match:
// Object result2 = (Object) toUpperCase.invokeExact("hello"); // WRONG
// Must be: String result2 = (String) toUpperCase.invokeExact("hello");
// invoke — allows auto-conversion (slower, more flexible)
Object result3 = toUpperCase.invoke("hello"); // auto-unboxes/casts
String result4 = (String) toUpperCase.invoke("hello");
// invokeWithArguments — takes a List or varargs (most flexible, slowest)
String result5 = (String) toUpperCase.invokeWithArguments(List.of("hello"));
// Calling a static method handle
MethodHandle parseInt = lookup.findStatic(
Integer.class, "parseInt", MethodType.methodType(int.class, String.class));
int n = (int) parseInt.invokeExact("42"); // exactly matches the MethodType
// Calling a constructor
MethodHandle newSB = lookup.findConstructor(
StringBuilder.class, MethodType.methodType(void.class, String.class));
StringBuilder sb = (StringBuilder) newSB.invoke("hello");
System.out.println(sb.toString()); // hello
Prefer invokeExact in hot code paths — it is the fastest invocation and can be fully inlined by the JIT. Use invoke or invokeWithArguments when types are not statically known or for convenience in less-critical code.
MethodHandles provides combinator methods to adapt and transform handles:
MethodHandles.Lookup lookup = MethodHandles.lookup();
// bindTo — partially apply (bind receiver or first argument)
MethodHandle toUpperCase = lookup.findVirtual(
String.class, "toUpperCase", MethodType.methodType(String.class));
MethodHandle helloUpper = toUpperCase.bindTo("hello");
// Now takes no arguments, always returns "HELLO"
String s = (String) helloUpper.invokeExact(); // "HELLO"
// asType — adapt the MethodType (with conversions)
MethodHandle parseInt = lookup.findStatic(
Integer.class, "parseInt", MethodType.methodType(int.class, String.class));
MethodHandle parseIntObj = parseInt.asType(
MethodType.methodType(Object.class, Object.class));
// Now accepts Object and returns Object (with auto-boxing/casting)
// dropArguments — insert ignored arguments at given positions
MethodHandle upper = lookup.findVirtual(String.class, "toUpperCase",
MethodType.methodType(String.class));
MethodHandle upperIgnoreInt = MethodHandles.dropArguments(
upper, 1, int.class); // now: (String, int) → String (ignores the int)
// insertArguments — partially apply by inserting fixed arguments
MethodHandle substring = lookup.findVirtual(
String.class, "substring", MethodType.methodType(String.class, int.class, int.class));
MethodHandle sub0to3 = MethodHandles.insertArguments(substring, 1, 0, 3);
// now: (String) → String, always calls substring(0, 3)
String prefix = (String) sub0to3.invokeExact("hello world"); // "hel"
// filterArguments — apply a handle to transform arguments before passing
MethodHandle trimmer = lookup.findVirtual(String.class, "trim",
MethodType.methodType(String.class));
MethodHandle filteredUpper = MethodHandles.filterArguments(upper, 0, trimmer);
// trimmer.invoke(s) is called first, then upper.invoke(trimmed)
String r = (String) filteredUpper.invokeExact(" hello "); // "HELLO"
// guardWithTest — conditional dispatch (if pred then trueHandle else falseHandle)
MethodHandle isNull = lookup.findStatic(
Objects.class, "isNull", MethodType.methodType(boolean.class, Object.class));
// MethodHandles.guardWithTest(test, trueHandle, falseHandle)
You can wrap existing java.lang.reflect objects into method handles using unreflect methods. This is useful when you already have a Method, Field, or Constructor from reflection:
// unreflect — wrap a java.lang.reflect.Method
Method method = String.class.getMethod("toUpperCase");
MethodHandle mh = MethodHandles.lookup().unreflect(method);
String result = (String) mh.invoke("hello"); // "HELLO"
// unreflectConstructor — wrap a Constructor
Constructor<ArrayList> ctor = ArrayList.class.getConstructor(int.class);
MethodHandle newList = MethodHandles.lookup().unreflectConstructor(ctor);
List<String> list = (List<String>) newList.invoke(16); // new ArrayList(16)
// unreflectGetter / unreflectSetter — wrap a Field
Field nameField = Person.class.getDeclaredField("name");
nameField.setAccessible(true);
MethodHandle getter = MethodHandles.lookup().unreflectGetter(nameField);
MethodHandle setter = MethodHandles.lookup().unreflectSetter(nameField);
Person p = new Person("Alice");
String name = (String) getter.invoke(p); // "Alice"
setter.invoke(p, "Bob"); // sets name to "Bob"
// privateLookupIn — access private members of another class (Java 9+)
// Requires the other class to open its package to your module
MethodHandles.Lookup privateLookup =
MethodHandles.privateLookupIn(OtherClass.class, MethodHandles.lookup());
MethodHandle privateMethod = privateLookup.findVirtual(
OtherClass.class, "privateMethod", MethodType.methodType(void.class));
MethodHandles are preferred over reflection in performance-critical code because the JIT compiler can inline and optimize them in ways that Method.invoke() cannot. They are the ideal foundation for implementing dynamic dispatch — building lookup tables of handlers keyed by type at startup, then invoking them on the hot path with near-zero overhead. Internally they power many core Java mechanisms: invokedynamic byte code, the lambda metafactory, and framework internals such as method routers and property accessors.
// Use case 1: High-performance event dispatcher
// Build a map of method handles indexed by event type at startup
Map<Class<?>, MethodHandle> handlers = new HashMap<>();
MethodHandles.Lookup lookup = MethodHandles.lookup();
// Register handlers by scanning annotations (once, at startup)
for (Method m : EventHandler.class.getMethods()) {
if (m.isAnnotationPresent(Handle.class)) {
Class<?> eventType = m.getParameterTypes()[0];
handlers.put(eventType, lookup.unreflect(m)
.bindTo(new EventHandler())); // bind receiver
}
}
// Dispatch (hot path — invokeExact is JIT-inlined)
void dispatch(Object event) throws Throwable {
MethodHandle handler = handlers.get(event.getClass());
if (handler != null) handler.invoke(event);
}
// Use case 2: Generic accessor without reflection overhead
class BeanAccessor {
private final MethodHandle getter;
private final MethodHandle setter;
BeanAccessor(Class<?> beanClass, String fieldName, Class<?> fieldType)
throws ReflectiveOperationException {
var lookup = MethodHandles.lookup();
this.getter = lookup.findGetter(beanClass, fieldName, fieldType);
this.setter = lookup.findSetter(beanClass, fieldName, fieldType);
}
Object get(Object bean) throws Throwable { return getter.invoke(bean); }
void set(Object bean, Object value) throws Throwable { setter.invoke(bean, value); }
}
Method handles checked at lookup time but types verified at invocation — a WrongMethodTypeException (unchecked) is thrown at runtime if types don't match. Always store handles in static final fields and compute them once during class initialization for maximum JIT optimization.