mystarrocks
mystarrocks

Reputation: 4088

Lambda - ClassNotFoundException

Here is what my code looks like, and it is unclear how/why executorService.submit(work::get) would throw a ClassNotFoundException on the anonymous class in question. It does not happen all the time, but once this exception is encountered, it does not seem to recover - subsequent requests are then met with the same exceptions. Anyone know what could be causing this to occur?

EDIT: I can confirm that either all calls to this method work, or none does, in a VM session - it is not like some succeed while others fail due to the said exception.

Further edit: https://bugs.openjdk.java.net/browse/JDK-8148560 is the exactly the bug I am experiencing, but that one was closed since it was not reproducible and/or the reporter did not respond. It somehow looks like the anonymous type resulting from the lambda expression is garbage collected before the executor gets to execute the expression, but obviously not always. The jdk in use is openjdk1.8.0_221.

package com.ab.cde.ct.service.impl;

@Service
public class IngestionService {
    @Autowired private TransactionTemplate transactionTemplate;
    @Autowired private AsyncTaskExecutor executorService;

    @Transactional
    public void ingest(Data data) {
        Supplier<Optional<String>> work = () -> transactionTemplate.execute(s -> {
            // actual work on the data object, enclosed in a try/catch/finally
        });
        executorService.submit(work::get); // this is where the exception gets thrown
    }
}

Here is what the exception stacktrace looks like (line nos. won't correspond since the code above is only a prototype):

2019-10-23 19:11:35,267|[http-apr-26001-exec-10]|[B6AC864143092042BBB4A0876BB51EB6.1]|[]|[ERROR] web.error.ErrorServlet  [line:142] org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.NoClassDefFoundError: com/ab/cde/ct/service/impl/IngestionService$$Lambda$53
org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.NoClassDefFoundError: com/ab/cde/ct/service/impl/IngestionService$$Lambda$53
    at org.springframework.web.servlet.DispatcherServlet.triggerAfterCompletionWithError(DispatcherServlet.java:1275)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:951)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:867)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:951)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:853)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:827)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
Caused by: java.lang.NoClassDefFoundError: com/ab/cde/ct/service/impl/IngestionService$$Lambda$53
    at com.ab.cde.ct.service.impl.IngestionService$$Lambda$53/812375226.get$Lambda(Unknown Source)
    at com.ab.cde.ct.service.impl.IngestionService.ingest(IngestionService.java:264)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    at com.sun.proxy.$Proxy252.ingest(Unknown Source)
Caused by: java.lang.ClassNotFoundException: com.ab.cde.ct.service.impl.IngestionService$$Lambda$53
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1364)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1185)
    ... 115 more

Upvotes: 8

Views: 1272

Answers (2)

TheJeff
TheJeff

Reputation: 4101

I've had this before with both DI issues and with ambiguity bugs/config problems in package resolution. I'm assuming from your post that the error happens after successful startup, and exactly on invocation of that line in the method, and can be hit in the debugger.

First suggestion:

With Gradle/Maven, check dependent packages to make sure everything has the version it needs, and you aren't overriding a version globally that may affect a dependent package that requires a higher or lower version of that dependency.

Some low hanging fruit to try first (if it is easy enough to pick):

  • Update your JDK version or Java version (or see if another dev on your team has a different version and they can repro the issue)
  • Update your version of spring (even a minor version)
  • Update your IDE
  • Add logging and check if the issue can be reproduced in release environments.

Regarding dependency injection,

I would recommend trying something like the following.. and is also a good practice for dependency injection in spring, as it gives spring a more explicit dependency map, and increases your ability to debug the application dependencies.

@Service
public class IngestionService {

    private TransactionTemplate transactionTemplate;
    private AsyncTaskExecutor executorService;

    public IngestionService(TransactionTemplate transactionTemplate, AsyncTaskExecutor executorService) {
         this.transactionTemplate = transactionTemplate;
         this.executorService = executorService;
    }

    @Transactional
    public void ingest(Data data) {
        Supplier<Optional<String>> work = () -> transactionTemplate.execute(s -> {
            // actual work on the data object, enclosed in a try/catch/finally
        });
        executorService.submit(work::get); // this is where the exception gets thrown
    }
}

There are a few reasons I recommend this:

  1. In java, when no constructor is defined, it is implied that there is a default constructor, and the compiler will generate a constructor for you. To spring, this can be confusing and also decrease performance.
  2. Defining this constructor explicitly tells Spring: I'm relying on these two dependencies which I also have setup as Beans that will be non-null and fully resolved on construction. You must initialize those dependencies first and pass them in before this can be a valid object.
  3. This helps in debugging, you can set a breakpoint in the constructor and validate what is coming in.
  4. If there is an issue with your bean setup for the dependencies, Spring will explode. The spring stack traces aren't always the most helpful, but it may help you debug any issue where you're not fully isolating and declaring beans you depend on in the correct way.
  5. It allows you to eliminate the possibility of any issues with injection, both from the Spring Framework point of view (hard to tell what happens behind the scenes), and from your application/domain logic point of view. If it is still null later, you'd have been able to debug what was passed in the constructor - meaning it either came in null, was de-allocated later, or there is an ambiguity problem where there may be two defined and spring will pass in the first one created even though there may eventually be multiple executorServices created.

As this should be a valid bean definition as long as the class is included in your configuration's component scan, you may need to define the bean explicitly in a configuration class, especially if you have multiple beans of each type (which could also be your problem)

Ex:

@Configuration
class SomeConfiguration {

    @Bean
    public IngestionService myIngestionServiceDefaultBeanNameChangeMe(TransactionTemplate transactionTemplateParamSentBySpringAutomaticallyChangeMyName, AsyncTaskExecutor executorServiceSentBySpringAutomaticallyChangeMyName) {
         return new IngestionService(transactionTemplateParamSentBySpringAutomaticallyChangeMyName, executorServiceSentBySpringAutomaticallyChangeMyName);
    }
}

Note that for the configuration, the params for the bean method will be sent in by spring automatically once those beans have been initialized within this config or another config. Pretty cool eh?

Also the name of your bean corresponds with the method name here, and if you have multiple beans of the same type spring could pass in as parameters, you may need to tell spring which bean name to use. To do this you'd utilize the @Qualifier annotation.

I really hope this helps, or at least validate instantiation is happening correctly.

Upvotes: 0

Ammar
Ammar

Reputation: 4024

This is the case of synthetic method generated by lambda which is unable to find the required class (i.e. TransactionCallback) and thus the below error

Caused by: java.lang.NoClassDefFoundError: com/ab/cde/ct/service/impl/IngestionService$$Lambda$53 at com.ab.cde.ct.service.impl.IngestionService$$Lambda$53/812375226.get$Lambda(Unknown Source)

The particular code which causes this issue is

Supplier<Optional<String>> work = () -> transactionTemplate.execute(s -> {
        // actual work on the data object, enclosed in a try/catch/finally
});

To get past this modify the code as below

TransactionCallback<Optional<String>> callback = transactionStatus -> {
      // your processing goes here  
      return Optional.of("some value"); 
};

Supplier<Optional<String>> work = () -> transactionTemplate.execute(callback);

If above still doesn't work use below workaround

Object callback = (TransactionCallback<Optional<String>>)transactionStatus -> {
     // your processing goes here     
     return Optional.of("some value");
};

Supplier<Optional<String>> work = () -> transactionTemplate.execute((TransactionCallback<Optional<String>>)callback);

Do let know in comments if any more information is required.

P.S.: There is no need for @Transactional if transactionTemplate is being used as both essentially serves the same purpose.

References:

  1. Lambda compilation here and here
  2. Synthetic methods in java

Upvotes: 5

Related Questions