vaadin / hilla

Build better business applications, faster. No more juggling REST endpoints or deciphering GraphQL queries. Hilla seamlessly connects Spring Boot and React to accelerate application development.
https://hilla.dev
Apache License 2.0
904 stars 57 forks source link

Hilla production build as WAR file not executable on servlet container #2678

Closed rbrki07 closed 1 month ago

rbrki07 commented 1 month ago

Describe the bug

A Hilla production build packaged as a WAR file is not executable on a servlet container like Apache Tomcat due to an error on deployment:

    java.lang.IllegalStateException: Error starting child
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:602)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:571)
       ...
    Caused by: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/hilla-production-build-test-1.0-SNAPSHOT]]
        at org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:402)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:179)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:599)
        ... 45 more
    Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'vaadinApplicationContextInitializer' defined in class path resource [com/vaadin/flow/spring/VaadinApplicationConfiguration.class]: 

Found app shell configuration annotations in non `AppShellConfigurator` classes.
Please create a custom class implementing `AppShellConfigurator` and move the following annotations to it:
    - @Theme from com.example.application.Application

Expected-behavior

A Hilla production build packaged as a WAR file should executable on a servlet container like Apache Tomcat.

Reproduction

  1. Create a project using npm init vaadin, choose Hilla (React + TypeScript) and Use pre release.
  2. Modify the new project as described here https://docs.spring.io/spring-boot/how-to/deployment/traditional-deployment.html#howto.traditional-deployment.war.
  3. Create a production build using mvn clean package -Pproduction
  4. Deploy WAR file to servlet container (like Apache Tomcat 10) and try to start the Hilla application.

System Info

Vaadin: 24.5.0.alpha8 Java: 21.0.2 (OpenJDK Runtime Environment GraalVM CE 21.0.2+13.1 - build 21.0.2+13-jvmci-23.1-b30) OS: macOS Sonoma 14.6.1

mcollovati commented 1 month ago

Looking at the error message, it seems your com.example.application.Application class has a @Theme annotation, but it does not implement AppShellConfigurator. You should make Application class implement AppShellConfigurator. If the Application class has other Spring related annotations, you might get errors during build. If so, create a new class implementing AppShellConfigurator and move all Vaadin annotations to that class.

rbrki07 commented 1 month ago

There is a @Theme annotation in com.example.application.Application, that is right. After applying the suggested changes of https://docs.spring.io/spring-boot/how-to/deployment/traditional-deployment.html#howto.traditional-deployment.war to be able to create a Production Build as WAR file com.example.application.Application looks like this:

package com.example.application;

import com.vaadin.flow.theme.Theme;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
@Theme(value = "hilla-production-build-test")
public class Application extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

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

Removing the @Theme annotation solves the problem and the WAR file is deployable on a servlet container, but this is obviously not a workaround ;)

Following your suggestion, I created a new class com.example.application.CustomAppShellConfigurator and moved the @Theme annotation into it:

package com.example.application;

import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.theme.Theme;

@Theme(value = "hilla-production-build-test")
public class CustomAppShellConfigurator implements AppShellConfigurator {}

If I create a new Production Build as a WAR file, I can now deploy it to a servlet container successfully. Thank you!

I wonder if this kind of adjustments should be mentioned in the docs like here https://vaadin.com/docs/latest/hilla/guides/production/servlet-containers or here https://vaadin.com/docs/latest/hilla/guides/production/spring-boot. What do you think?

mcollovati commented 1 month ago

To be honest, I don't know what's the suggested way to style Hilla applications. Anyway, I think that the Styling guide could have a link to the Application Theme docs is that is the way to go. Otherwise, it could have a note about removing the @Theme annotation if it should not be used.

The Hilla team may help clarify the question.

rbrki07 commented 1 month ago

To me, it is less a question about the suggested way to style a Hilla application. I'm looking at this from a more general perspective: I created a Hilla app using one of the recommended ways (in this case npm init vaadin). The generated code has to be modified to support a Production Build as WAR file. The official Hilla docs only refers to Spring Boot docs about the required changes to be made to create a WAR file. As a developer, I'm left with a stack trace that shows me, that obviously more has to be done, and the additional changes have to be made in the generated code in a way that is not mentioned in Vaadin's docs. I guess this problem also applies for Flow applications.

mcollovati commented 1 month ago

The Application class created with npm init vaadin implements AppShellConfigurator. To better understand, did you remove the interface because it was not present in the Spring documentation?

rbrki07 commented 1 month ago

Yes, that's what I did. Merging the generated code with the recommended changes in Spring Boot docs would result in:

package com.example.application;

import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.theme.Theme;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
@Theme(value = "hilla-production-build-test")
public class Application extends SpringBootServletInitializer implements AppShellConfigurator {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

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

This works as expected. Sorry for the trouble, I took the instructions from Spring Boot too literally.