Building Production Mode with Gradle Not Working

Cannot get production build to run using Spring Boot, Vaadin 24.7. We have set every possible property every possible way. Does not work. The problem appears to be in the vaadinBuildFrontend task. It builds DEV mode no matter what config. We even used the properties in the build commands and added them to the run script.

We have spent an enormous amount of time troubleshooting this. Vaadin seems broken.

Here is the error:

Jul 03, 2025 11:53:50 AM org.apache.catalina.core.StandardContext listenerStart
SEVERE: Exception sending context initialized event to listener instance of class [com.vaadin.flow.spring.VaadinServletContextInitializer$CompositeServletContextListener]
java.lang.RuntimeException: Unable to initialize com.vaadin.flow.spring.VaadinServletContextInitializer$DevModeServletContextListener
        at com.vaadin.flow.spring.VaadinServletContextInitializer$FailFastServletContextListener.contextInitialized(VaadinServletContextInitializer.java:206)
        at com.vaadin.flow.spring.VaadinServletContextInitializer$CompositeServletContextListener.lambda$contextInitialized$0(VaadinServletContextInitializer.java:230)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at com.vaadin.flow.spring.VaadinServletContextInitializer$CompositeServletContextListener.contextInitialized(VaadinServletContextInitializer.java:230)
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4044)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4474)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
        at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
        at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
        at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
        at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
        at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
        at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
        at org.apache.catalina.core.StandardService.startInternal(StandardService.java:412)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
        at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:870)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
        at org.apache.catalina.startup.Tomcat.start(Tomcat.java:438)
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:128)
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:107)
        at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:517)
        at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:219)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:193)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:167)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:621)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:753)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:318)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1362)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1351)
        at com.extrospectre.web.Application.main(Application.java:65)
Caused by: java.lang.RuntimeException: no DevModeHandlerManager implementation found but but dev server enabled. Either disable by setting vaadin.frontend.hotdeploy=false (and run the build-frontend maven goal) or include the vaadin-dev-server dependency
        at com.vaadin.flow.spring.VaadinServletContextInitializer$DevModeServletContextListener.failFastContextInitialized(VaadinServletContextInitializer.java:528)
        at com.vaadin.flow.spring.VaadinServletContextInitializer$FailFastServletContextListener.contextInitialized(VaadinServletContextInitializer.java:202)
        ... 41 more

Does this code work at your machine? GitHub - vaadin/base-starter-spring-gradle: Starting point for a Spring Boot based Vaadin application using Gradle build

After looking at that project we noticed this difference:

configurations {
	production {
		exclude group: 'com.vaadin', module: 'vaadin-dev'
		exclude group: 'com.vaadin', module: 'vaadin-dev-bundle'
	}
	
	developmentOnly
	
	runtimeClasspath {
		if (project.ext.production) {
			extendsFrom production
		} 
		else {
			extendsFrom developmentOnly
		}
	}
}

The production section does not do what its supposed to do. Taking that out makes bootRun work but that led to the next error:

Jul 03, 2025 12:20:07 PM org.apache.catalina.core.StandardContext listenerStart
SEVERE: Exception sending context initialized event to listener instance of class [com.vaadin.flow.spring.VaadinServletContextInitializer$CompositeServletContextListener]
java.lang.RuntimeException: Unable to initialize com.vaadin.flow.spring.VaadinServletContextInitializer$DevModeServletContextListener
        at com.vaadin.flow.spring.VaadinServletContextInitializer$FailFastServletContextListener.contextInitialized(VaadinServletContextInitializer.java:206)
        at com.vaadin.flow.spring.VaadinServletContextInitializer$CompositeServletContextListener.lambda$contextInitialized$0(VaadinServletContextInitializer.java:230)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at com.vaadin.flow.spring.VaadinServletContextInitializer$CompositeServletContextListener.contextInitialized(VaadinServletContextInitializer.java:230)
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4044)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4474)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
        at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
        at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
        at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
        at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
        at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
        at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
        at org.apache.catalina.core.StandardService.startInternal(StandardService.java:412)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
        at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:870)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
        at org.apache.catalina.startup.Tomcat.start(Tomcat.java:438)
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:128)
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:107)
        at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:517)
        at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:219)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:193)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:167)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:621)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:753)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:318)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1362)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1351)
        at app.Application.main(Application.java:65)
Caused by: java.lang.IllegalStateException: Failed to determine project directory for dev mode. Directory '<path>\build\install\Website\bin' does not look like a Maven or Gradle project. Ensure that you have run the prepare-frontend Maven goal, which generates 'flow-build-info.json', prior to deploying your application
        at com.vaadin.flow.server.AbstractConfiguration.getProjectFolder(AbstractConfiguration.java:238)
        at com.vaadin.flow.server.AbstractConfiguration.getJavaResourceFolder(AbstractConfiguration.java:261)
        at com.vaadin.base.devserver.startup.DevModeInitializer.initDevModeHandler(DevModeInitializer.java:229)
        at com.vaadin.base.devserver.DevModeHandlerManagerImpl.initDevModeHandler(DevModeHandlerManagerImpl.java:106)
        at com.vaadin.flow.spring.VaadinServletContextInitializer$DevModeServletContextListener.failFastContextInitialized(VaadinServletContextInitializer.java:593)
        at com.vaadin.flow.spring.VaadinServletContextInitializer$FailFastServletContextListener.contextInitialized(VaadinServletContextInitializer.java:202)
        ... 41 more

The template project tells you to do this:

cd build/libs/
java -jar base-starter-spring-gradle*.jar

That will not work in a production deployed product. We need the shell script produced by the installDist task to work so the working dir is correct and other things. The spring start scripts still do not work even though bootRun does.

We are running the installDist task to create the install folder with scripts.

@marcoc_753 might have some ideas with his latest Gradle debugging some months ago :grimacing:

That Configuration block I showed at the top SHOULD work but does not. If you just follow that template project version, the bootRun works but the dev mode jars are still included, so not really working.

This doesn’t work either:

implementation(libs.vaadin.core) {
	exclude group: 'com.vaadin', module: 'vaadin-dev'
	exclude group: 'com.vaadin', module: 'vaadin-dev-bundle'
}

I think the Vaadin Gradle plugin needs major work.

I prefer to just remove vaadin-dev and add it for the developmentOnly configuration.

But i’ts hard to guess from the fragments. Can you share a MRE?

E.g.

dependencies {
    implementation(platform(libs.springboot.bom))
    implementation(platform(libs.vaadin.bom))
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("com.vaadin:vaadin-spring-boot-starter", {
        exclude(group = "com.vaadin", module = "hilla") // remove for hilla
        exclude(group = "com.vaadin", module = "flow-react") // remove for react router and components in the client
        exclude(group = "com.vaadin", module = "vaadin-dev")
    })

    developmentOnly(platform(libs.springboot.bom))
    developmentOnly(platform(libs.vaadin.bom))
    developmentOnly("org.springframework.boot:spring-boot-devtools")
    developmentOnly("com.vaadin:vaadin-dev", {
        exclude(group = "com.vaadin", module = "hilla-dev") // remove for hilla
        exclude(group = "com.vaadin", module = "copilot")
    })
}

configurations {
    developmentOnly
    runtimeClasspath {
        extendsFrom(configurations.developmentOnly.get())
    }
}

See Making sure you're not a bot!

1 Like

I’m not so familiar with Gradle, so I don’t really understand the posted configurations, but looking at the exception it seems vaadinBuildFrontend did not run (or did not run correctly). I guess you activate production mode by setting vaadin.productionMode=true in the build file or passing it to the command line.

Anyway, if you can provide an example project with your configuration, we can take a look and try to improve the plugin, if needed.

If you have any idea on how to make the plugin better, please create tickets on the Vaadin Flow repository. Of course, also code contributions are more than welcome.

Thank you. I will do that as soon as I can. The problem is that when you run the installed app via the Spring start scripts, it thinks its still in dev mode. These scripts are created by the Gradle installDist task. We already have to customize those scripts in our build.gradle because Spring expects the application.properties to be in the current working dir, which is bad for an installed application. The vaadin-dev and vaadin-dev-bundle jars keep getting deployed despite building for production. We’ve tried manually removing them but still get errors, the last one I posted. Its not switching to production mode correctly when built with Gradle.

We found the problems. First, we switched how we specified the Vaadin dependencies to (we are using the gradle .toml file):

	implementation libs.vaadin.spring.boot.starter
	developmentOnly libs.spring.boot.devtools

We corrected the configurations block:

configurations {
	// don't include dev mode deps
	/*
	production {
		exclude group: 'com.vaadin', module: 'vaadin-dev'
		exclude group: 'com.vaadin', module: 'vaadin-dev-bundle'
	}
	*/
	
	developmentOnly
	
	// this only effects the runtime classpath, not the included jars
	runtimeClasspath {
		/*if (project.ext.production) {
			extendsFrom production
		} 
		else {*/
			extendsFrom developmentOnly
		/*}*/
	}
    
    all {
    	// remove Spring's logback dependency
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
        exclude group: 'ch.qos.logback', module: 'logback-classic'
        exclude group: 'ch.qos.logback', module: 'logback-core'
    }
}

But this was the primary problem:

tasks.named('startScripts') {
	doLast {
        def unixScript = file("${outputDir}/${applicationName}")
        def windowsScript = file("${outputDir}/${applicationName}.bat")

        def mainClassName = project.application.mainClass.get()
        def simpleName = mainClassName.tokenize('.').last()
        //def optsEnvVar = simpleName.toUpperCase().replaceAll('[^A-Z0-9]', '_') + "_OPTS"
        def optsEnvVar = "${project.ext.artifactId}".toUpperCase().replaceAll('[^A-Z0-9]', '_') + "_OPTS"
        
        def customValue = '-Dspring.config.location=%APP_HOME%\\\\conf\\\\application.properties'

		if ( System.properties['os.name'].toLowerCase().contains('windows') ) {
	        windowsScript.text = windowsScript.text.replaceFirst(
	            /(?m)^set DEFAULT_JVM_OPTS=/,
	            "set ${optsEnvVar}=${customValue}\r\nset DEFAULT_JVM_OPTS="
	        )
		} 
		else {
	        unixScript.text = unixScript.text.replaceFirst(
	            /(?m)^DEFAULT_JVM_OPTS=/,
	            "${optsEnvVar}=\"${customValue}\"\nDEFAULT_JVM_OPTS="
	        )
		}
	}
}

That code is necessary because Spring expects the application.properties to be in the current dir, which would be the bin dir, and that is not appropriate for a deployed product. So we modify the script. The gradle installDist which creates this script needs to be enhanced to provide an easier way to tell Spring where that file is located.

There were a few other cosmetic things as well.

The Spring logging led us in the wrong direction as usual. That’s the penalty for using Spring.