Search code examples
springjava-8java-streamaspectjaspectj-maven-plugin

Aspectj BootstrapMethodError when using Java 8 stream API


so here i am - running a spring application with spring roo behind.

i use to cut my controllers into aspects, so my main controller will look like this:

@Controller
@RequestMapping("/apples")
@SessionAttributes(types = {Apple.class})
public class AppleController {
}

and other aspects extend its functionality like:

privileged aspect AppleController_Basics {

    @RequestMapping(value = "/allApples", produces = "text/html", method=RequestMethod.GET)
    public String AppleController.allApples(Model model) {
       ...
       return "apples/list";
    }
}

Now when i try to use Java 8 stream API within the aspect like:

apples.stream().filter(a -> a.isSweet()).collect(Collectors.toList());

i am facing the following exception:

org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.BootstrapMethodError: java.lang.NoSuchMethodError: com.apple.web.AppleController.lambda$0(Lcom/apple/model/Apple;)Z

when i use stream API for another entity than Apple itself, i get a slight different exception:

org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.BootstrapMethodError: java.lang.IllegalAccessError: tried to access method com.apple.web.AppleController.lambda$0(Lcom/apple/security/AppleEater;)Z from class com.apple.web.aspects.AppleController_Basics

when using forEach i get OutOfMemoryError::

apples.forEach(System.out::println);

org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.OutOfMemoryError: Java heap space

when i use those expressions in the main class, everything works fine.

the plugin looks like this:

<plugin>
   <groupId>org.codehaus.mojo</groupId>
   <artifactId>aspectj-maven-plugin</artifactId>
   <version>1.9</version>
   <dependencies>
      <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjrt</artifactId>
         <version>1.8.10</version>
      </dependency>
      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjtools</artifactId>
          <version>1.8.10</version>
      </dependency>
   </dependencies>
   <executions>
      <execution>
         <phase>process-sources</phase>
         <goals>
            <goal>compile</goal>
         </goals>
      </execution>
   </executions>
   <configuration>
      <complianceLevel>1.8</complianceLevel>
      <outxml>true</outxml>
      <aspectLibraries>
         <aspectLibrary>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
         </aspectLibrary>
      </aspectLibraries>
      <source>1.8</source>
      <target>1.8</target>
      <showWeaveInfo>true</showWeaveInfo>
      <weaveWithAspectsInMainSourceFolder>false</weaveWithAspectsInMainSourceFolder>
   </configuration>
</plugin>

i tried diffrent things to change my aspectj plugin configuration in order to make it work - without success. i appreciate any hint or help as i am really confused right now, pls dont hate <3

javap -c -p AppleController.class

public java.lang.String allApples(org.springframework.ui.Model);
Code:
  0: aload_0
  1: aload_1
  2: invokestatic  #528                // Method com/apple/web/aspects/AppleController_Basics.ajc$interMethod$com_apple_web_aspects_AppleController_Basics$com_apple_web_AppleController$allApples:(Lcom/apple/web/AppleController;Lorg/springframework/ui/Model;)Ljava/lang/String;
  5: areturn

Solution

  • This is obviously an AspectJ compiler bug or shortcoming. I have created a bug ticket for it.

    Here is the (non-Spring) test case I have extracted from your code:

    package de.scrum_master.app;
    
    public class Apple {
      private String type;
      private boolean sweet;
    
      public Apple(String type, boolean sweet) {
        this.type = type;
        this.sweet = sweet;
      }
    
      public String getType() {
        return type;
      }
    
      public boolean isSweet() {
        return sweet;
      }
    }
    
    package de.scrum_master.app;
    
    import java.util.Arrays;
    import java.util.List;
    
    public class AppleController {
      private static final List<Apple> APPLES =
        Arrays.asList(new Apple("Granny Smith", false), new Apple("Golden Delicious", true));
    
      public static void main(String[] args) {
        AppleController appleController = new AppleController();
        System.out.println("Named: " + appleController.namedApples(APPLES, "Smith"));
        System.out.println("Sweet: " + appleController.sweetApples(APPLES));
        System.out.println("Sour:  " + appleController.sourApples(APPLES));
      }
    }
    
    package de.scrum_master.aspect;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    import java.util.function.Predicate;
    import de.scrum_master.app.Apple;
    import de.scrum_master.app.AppleController;
    
    public privileged aspect AppleControllerITDAspect {
      public List<Apple> AppleController.namedApples(List<Apple> apples, String subString) {
        // Anonymous subclass works
        return apples.stream().filter(new Predicate<Apple>() {
          @Override
          public boolean test(Apple a) {
            return a.getType().contains(subString);
          }
        }).collect(Collectors.toList());
      }
    
      public List<Apple> AppleController.sweetApples(List<Apple> apples) {
        // Method reference works
        return apples.stream().filter(Apple::isSweet).collect(Collectors.toList());
      }
    
      public List<Apple> AppleController.sourApples(List<Apple> apples) {
        // Lambda causes IllegalAccessError
        return apples.stream().filter(a -> !a.isSweet()).collect(Collectors.toList());
      }
    }
    

    The console log looks like this:

    Named: [de.scrum_master.app.Apple@6f496d9f]
    Sweet: [de.scrum_master.app.Apple@4e50df2e]
    Exception in thread "main" java.lang.BootstrapMethodError: java.lang.IllegalAccessError: tried to access method de.scrum_master.app.AppleController.lambda$0(Lde/scrum_master/app/Apple;)Z from class de.scrum_master.aspect.AppleControllerITDAspect
        at de.scrum_master.aspect.AppleControllerITDAspect.ajc$interMethod$de_scrum_master_aspect_AppleControllerITDAspect$de_scrum_master_app_AppleController$sourApples(AppleControllerITDAspect.aj:28)
        at de.scrum_master.app.AppleController.sourApples(AppleController.java:1)
        at de.scrum_master.aspect.AppleControllerITDAspect.ajc$interMethodDispatch1$de_scrum_master_aspect_AppleControllerITDAspect$de_scrum_master_app_AppleController$sourApples(AppleControllerITDAspect.aj)
        at de.scrum_master.app.AppleController.main(AppleController.java:14)
    Caused by: java.lang.IllegalAccessError: tried to access method de.scrum_master.app.AppleController.lambda$0(Lde/scrum_master/app/Apple;)Z from class de.scrum_master.aspect.AppleControllerITDAspect
        at java.lang.invoke.MethodHandleNatives.resolve(Native Method)
        at java.lang.invoke.MemberName$Factory.resolve(Unknown Source)
        at java.lang.invoke.MemberName$Factory.resolveOrFail(Unknown Source)
        at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(Unknown Source)
        at java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(Unknown Source)
        at java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(Unknown Source)
        ... 4 more
    

    In the aspect above you can also see a temporary workaround: use a method reference or a classical anonymous subclass instead of a lambda.

    Background info: The AspectJ compiler AJC is a regularly updated fork of the Eclipse Java compiler ECJ (AspectJ is also an official Eclipse project, BTW). So maybe the bug is in ECJ, but probably rather in AJC.