Lambda expression fails with a java.lang.BootstrapMethodError at runtime

generics, java, lambda, package-private

Solution

As far as I see, JVM does everything right.

When `apply` method is declared in `Applicable`, but not in `SomeApplicable`, the anonymous class should work, and the lambda should not. Let's examine the bytecode.

Anonymous class Test$1

public void apply(a.SomeApplicable);
  Code:
     0: getstatic     #2    // Field java/lang/System.out:Ljava/io/PrintStream;
     3: ldc           #3    // String a
     5: invokevirtual #4    // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     8: return

public void apply(a.Applicable);
  Code:
     0: aload_0
     1: aload_1
     2: checkcast     #5    // class a/SomeApplicable
     5: invokevirtual #6    // Method apply:(La/SomeApplicable;)V
     8: return

`javac` generates both the implementation of interface method `apply(Applicable)` and the overriden method `apply(SomeApplicable)`. Neither of methods refer to inaccessible interface `Applicable`, except in the method signature. That is, `Applicable` interface is not resolved (JVMS §5.4.3) anywhere in the code of anonymous class.

Note that `apply(Applicable)` can be successfully called from `Test`, because types in the method signature are not resolved during the resolution of `invokeinterface` instruction (JVMS §5.4.3.4).

Lambda

An instance of lambda is obtained by execution of `invokedynamic` bytecode with the bootstrap method `LambdaMetafactory.metafactory`:

BootstrapMethods:
  0: #36 invokestatic java/lang/invoke/LambdaMetafactory.metafactory
    Method arguments:
      #37 (La/Applicable;)V
      #38 invokestatic b/Test.lambda$main$0:(La/SomeApplicable;)V
      #39 (La/SomeApplicable;)V

The static arguments used to construct lambda are:

- MethodType of the implemented interface: `void (a.Applicable)`;

- Direct MethodHandle to the implementation;

- Effective MethodType of the lambda expression: `void (a.SomeApplicable)`.

All these arguments are resolved during `invokedynamic` bootstrap process (JVMS §5.4.3.6).

Now the key point: to resolve a MethodType all classes and interfaces given in its method descriptor are resolved (JVMS §5.4.3.5). In particular, JVM tries to resolve `a.Applicable` on behalf of `Test` class, and fails with `IllegalAccessError`. Then, according to the spec of `invokedynamic`, the error is wrapped into `BootstrapMethodError`.

Bridge method

To work around `IllegalAccessError`, you need to explicitly add a bridge method in publicly accessible `SomeApplicable` interface:

public interface SomeApplicable extends Applicable<SomeApplicable> {
    @Override
    void apply(SomeApplicable self);
}

In this case lambda will implement `apply(SomeApplicable)` method instead of `apply(Applicable)`. The corresponding `invokedynamic` instruction will refer to `(La/SomeApplicable;)V` MethodType, which will be successfully resolved.

Note: it is not enough to change just `SomeApplicable` interface. You'll have to recompile `Test` with the new version of `SomeApplicable` in order to generate `invokedynamic` with the proper MethodTypes. I've verified this on several JDKs from 8u31 to the latest 9-ea, and the code in question worked without errors.

Problem

In one package (`a`) I have two functional interfaces: ``` package a; @FunctionalInterface interface Applicable<A extends Applicable<A>> { void apply(A self); } ``` ``` package a; @FunctionalInterface public interface SomeApplicable extends Applicable<SomeApplicable> { } ``` The `apply` method in the superinterface takes `self` as an `A` because otherwise, if `Applicable<A>` was used instead, the type would not be visible outside the package and therefore the method couldn't be implemented. In another package (`b`), I have the following `Test` class: ``` package b; import a.SomeApplicable; public class Test { public static void main(String[] args) { // implement using an anonymous class SomeApplicable a = new SomeApplicable() { @Override public void apply(SomeApplicable self) { System.out.println("a"); } }; a.apply(a); // implement using a lambda expression SomeApplicable b = (SomeApplicable self) -> System.out.println("b"); b.apply(b); } } ``` The first implementation uses an anonymous class and it works with no problem. The second one, on the other hand, compiles fine but fails at runtime throwing a `java.lang.BootstrapMethodError` caused by a `java.lang.IllegalAccessError` as it tries to access the `Applicable` interface. ``` Exception in thread "main" java.lang.BootstrapMethodError: java.lang.IllegalAccessError: tried to access class a.Applicable from class b.Test at b.Test.main(Test.java:19) Caused by: java.lang.IllegalAccessError: tried to access class a.Applicable from class b.Test ... 1 more ``` I think it would make more sense if the lambda expression either worked just like the anonymous class or gave a compile-time error. So, I'm just wondering what is going on here. I tried removing the superinterface and declaring the method within `SomeApplicable` like this: ``` package a; @FunctionalInterface public interface SomeApplicable { void apply(SomeApplicable self); } ``` This obviously makes it work but allows us to see what's different in bytecode. The synthetic `lambda$0` method compiled from the lambda expression seems identical in both cases, but I could spot one difference in the method arguments under bootstrap methods. ``` Bootstrap methods: 0 : # 58 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #59 (La/Applicable;)V #62 invokestatic b/Test.lambda$0:(La/SomeApplicable;)V #63 (La/SomeApplicable;)V ``` The `#59` changes from `(La/Applicable;)V` to `(La/SomeApplicable;)V`. I don't really know how lambda metafactory works but I think this might be a key difference. I also tried explicitly declaring the `apply` method in `SomeApplicable` like this: ``` package a; @FunctionalInterface public interface SomeApplicable extends Applicable<SomeApplicable> { @Override void apply(SomeApplicable self); } ``` Now the method `apply(SomeApplicable)` actually exists and the compiler generates a bridge method for `apply(Applicable)`. Still the same error is thrown at runtime. At bytecode level it now uses `LambdaMetafactory.altMetafactory` instead of `LambdaMetafactory.metafactory`: ``` Bootstrap methods: 0 : # 57 invokestatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; Method arguments: #58 (La/SomeApplicable;)V #61 invokestatic b/Test.lambda$0:(La/SomeApplicable;)V #62 (La/SomeApplicable;)V #63 4 #64 1 #66 (La/Applicable;)V ```

Original source

Related problems