Search code examples
javaspring-bootgradlemulti-modulevaadin8

Application started with Spring Boot "bootRun" causes NoClassDefFoundError when including Gradle Vaadin plugin


My goal is to set up a simple multi-module-project which uses Spring Boot 2, Gradle 4.x and Vaadin 8.x as UI. Vaadin is currently used in only one of the sub-projects, the other ones provide services or are just libraries.

I started with this really nice Spring tutorial, which (albeit using older Gradle syntax) produced a working test project to start with. It contains a "libaray" sub-project and an "application" sub-project depending on the former. Both "bootRun" and "bootJar" Gradle tasks worked like a charm and still worked after I centralized some of the configuration in a parent build.grade.

Then I added the Vaadin Gradle Plugin to the "application" project and changed the endpoint to show a simple vaadin label.

The problem is, that now when using "bootRun", the started application is no longer able to access the classes from "library" it depends on. If I remove any dependency in the code, the application works but as soon as I reference a class from "library" (e.g. "MyService") it crashes with "java.lang.ClassNotFoundException".

However, when deploying the same application with "bootJar" and running the jar, everything works, so the inter-module-depencies can be resolved.

Now I wonder if I have a lack of understanding and the Vaadin Gradle plugin requires additional/different configuration for module dependencies? Or could this be a bug in the Vaadin Gradle plugin or a problem in my configuration?

I have read the complete documentation of this plugin without getting a clue, have already searched the web but neither did I find the "NoClassDefFoundError" in this specific context nor did I find a working multi-module-project sample with both Spring Boot and Vaadin 8.

Here is the relevant sample code (excluding the imports):

hello.app.DemoApplication

@SpringBootApplication(scanBasePackages = "hello")
@RestController
public class DemoApplication {
    //Service defined in dependent sub-project
    private final MyService myService;

    public DemoApplication(MyService myService) {
        this.myService = myService;
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

hello.app.StartUI

/** Entry point for the Vaadin 8 UI */
@SpringUI
@SpringView
public class StartUI extends UI implements View {

    @Override
    protected void init(final VaadinRequest request) {
        setContent(new Label("Hello vaadin"));
    }
}

library gradle.properties

plugins { id "io.spring.dependency-management" version "1.0.5.RELEASE" }
ext { springBootVersion = '2.0.3.RELEASE' }
jar {
    baseName = 'library'
    version = '0.0.1-SNAPSHOT'
}
dependencies {
    implementation('org.springframework.boot:spring-boot-starter')
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}
dependencyManagement {
    imports { mavenBom("org.springframework.boot:spring-boot-dependencies:${springBootVersion}") }
}

application gradle.properties

plugins {
    id "io.spring.dependency-management" version "1.0.5.RELEASE"
    id "org.springframework.boot" version "2.0.3.RELEASE"
    id 'com.devsoap.plugin.vaadin'version '1.3.1' //Add vaadin support
}
bootJar {
    baseName = 'application'
    version = '0.0.1-SNAPSHOT'
}
dependencies {
    implementation project(':library') // Contains "MyService"
    implementation('org.springframework.boot:spring-boot-starter-actuator')
    implementation('org.springframework.boot:spring-boot-starter-web')
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}

parent (root) project build.gradle

subprojects {
    //Only plugins configured here applied to sub-projects.
    apply plugin: "java-library"
    apply plugin: "eclipse"
    repositories { mavenCentral() } 
    sourceCompatibility = 1.8
}

The parent settings.gradle references the child projects, of course.

The stack trace is (somewhat shortened)

java.lang.IllegalStateException: Cannot load configuration class: hello.app.DemoApplication
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:414) ~[spring-context-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanFactory(ConfigurationClassPostProcessor.java:254) ~[spring-context-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:284) ~[spring-context-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:128) ~[spring-context-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:694) ~[spring-context-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    ...
Caused by: java.lang.IllegalStateException: Unable to load cache item
    at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:79) ~[spring-core-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:116) ~[spring-core-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291) ~[spring-core-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:480) ~[spring-core-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:337) ~[spring-core-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassEnhancer.createClass(ConfigurationClassEnhancer.java:138) ~[spring-context-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassEnhancer.enhance(ConfigurationClassEnhancer.java:110) ~[spring-context-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:403) ~[spring-context-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    ... 12 common frames omitted
Caused by: java.lang.NoClassDefFoundError: hello/service/MyService
    at java.lang.Class.getDeclaredConstructors0(Native Method) ~[na:1.8.0_151]
    at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671) ~[na:1.8.0_151]
    at java.lang.Class.getDeclaredConstructors(Class.java:2020) ~[na:1.8.0_151]
    at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:566) ~[spring-core-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    ...
Caused by: java.lang.ClassNotFoundException: hello.service.MyService
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_151]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_151]
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335) ~[na:1.8.0_151]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_151]
    ... 34 common frames omitted

Further information: I'm using Buildship 2.2 plugin with Gradle 4.3 in Eclipse Photon.


Solution

  • A few things that I'm seeing:

    • You are not building a Java library but Java web application, so leave out the 'java-library' plugin.

    • A better strategy for managing your plugins is to register them all in the parent project with the apply false parameter. Then in the child projects apply the plugin normally. That way you keep all the plugin dependency versions managed in one place

    • The implementation configuration is rather new and many plugins does not support it (for example gretty). I would switch those to use compile and testCompile instead.

    • The Spring Dependency Management plugin is really intrusive and not all plugins support it (Gradle Vaadin Plugin for one). I would skip it and instead use Gradle's BOM support instead. https://docs.gradle.org/4.6/userguide/managing_transitive_dependencies.html#sec:bom_import