WrongMethodTypeException with MethodHandle.invokeExact() but no arguments and return type is same

119 views Asked by At

I'm using Java 17 on AWS Lambda. I have an interface Foo with a method bar(), returning a CompletableFuture<Map<Bar, Long>>:

public interface Foo {
  public CompletableFuture<Map<Bar, Long>> bar();
}

In the MyProcessor class, I have an instance reference to an implemention of Foo which is FooImpl:

class MyProcessor {

  private final Foo foo;

  MyProcessor() {
    foo = goGetFooImpl();
  }

  void doSomething() {
    …
  }

}

Finally in MyProcessor.doSomething(), I look up the Foo.bar() method and try to invoke it using a method handle (leaving out exception handling for clarity):

  //filter out the only method with the name "bar"
  Method barMethod = foo.getClass().getDeclaredMethods().…;
  MethodHandle = MethodHandles.lookup().unreflect(barMethod);
  CompletableFuture<?> result = (CompletableFuture<?>)methodHandle.invokeExact(foo);

The error message is odd:

java.lang.invoke.WrongMethodTypeException: expected (FooImpl)CompletableFuture but found (Foo)CompletableFuture

I realize with MethodHandle.invokeExact() that the arguments and return type must be exact. In this case there are no arguments. And it is my understanding that the generic type of CompletableFuture is erased at runtime; thus in this case the concrete return type is the same. So what is the problem?

What does the error message mean when it mentions (Fooimpl) and (Foo)? How does the type of the target impact the type of CompletableFuture?

I realize that the bar() method is declared on Foo, but looked up that method on the class of an instance of FooImpl. But why would that be a problem? From the documentation I had understood that MethodHandle.invokeExact() works on virtual methods.

I can call methodHandle.invoke(foo) here and it works fine, but not methodHandle.invokeExact(foo). What is not "exact" about what I'm doing? Does anyone know what the error message means?

I found MethodHandle cast return type which appears to be related, but I'm still not getting it. A CompletableFuture is a CompletableFuture. Since when is there such things as (Foo)CompletableFuture and (FooImpl)CompletableFuture and why would they be different?

1

There are 1 answers

10
Jorn Vernee On

invokeExact really means that the static type of the argument must match the type of the MethodHandle exactly. Foo and FooImpl are not the same type. See also the doc of invokeExact:

The symbolic type descriptor at the call site of invokeExact must exactly match this method handle's type. No conversions are allowed on arguments or return values.

The symbolic descriptor for invokeExact is derived from the static types of the arguments and return value at the call site. In your case that is (Foo)CompletableFuture, taking 1 argument of type Foo and returning CompletableFuture (you could also inspect the type descriptor using javap to disassemble the class file). See also the doc section on Signature Polymorphism: https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/invoke/MethodHandle.html#sigpoly

invoke automatically applies the type conversion from Foo to FooImpl, so that works.

If you're only invoking the MethodHandle once using invoke is fine. If you plan on invoking it many times you could erase the type of the receiver to Foo to avoid the type conversion:

//filter out the only method with the name "bar"
Method barMethod = foo.getClass().getDeclaredMethods().…;
MethodHandle methodHandle = MethodHandles.lookup().unreflect(barMethod);
methodHandle = methodHandle.asType(methodHandle.type().changeParameterType(0, Foo.class));
CompletableFuture<?> result = (CompletableFuture<?>)methodHandle.invokeExact(foo);