Vaadin servlet not starting when launched from embedded Jetty in Production

My Vaadin 14 app has been working pretty well in development mode when launched from eclipse. When I create a jar and run with vaadin.productionMode=true I get this:

2020-07-06 16:39:32.145 [main]
 DEBUG Unable to fully determine correct flow-build-info.
Accepting file 'file:/C:/Program%20Files/AAA/YYY/lib/WebApp-6.0.7.jar!/META-INF/VAADIN/config/flow-build-info.json'
2020-07-06 16:39:32.161 [main]
 DEBUG Skipping DEV MODE because PRODUCTION MODE is set.
2020-07-06 16:39:32.193 [main]
 INFO  Initializing AtmosphereFramework
2020-07-06 16:39:32.208 [main]
 DEBUG Unable to fully determine correct flow-build-info.
Accepting file 'file:/C:/Program%20Files/AAA/YYY/lib/WebApp-6.0.7.jar!/META-INF/VAADIN/config/flow-build-info.json'
2020-07-06 16:39:32.208 [main]
 DEBUG Unable to fully determine correct flow-build-info.
Accepting file 'file:/C:/Program%20Files/AAA/YYY/lib/WebApp-6.0.7.jar!/META-INF/VAADIN/config/flow-build-info.json'
2020-07-06 16:39:32.208 [main]
 INFO  Skipping automatic servlet registration because there are no navigation targets registered to the route registry and there are no web component exporters.

The Vaadin servlet is not loaded unlike running from eclipse in dev mode. There’s no attempt or errors after this relating to vaadin.

I’ve modeled my code after the sample https://github.com/mvysny/vaadin14-embedded-jetty-gradle though my code is a bit more complex so I can’t post all of it here. This is the WebAppContext portion:

WebAppContext context = new WebAppContext();
/*
 * We could context.addServlet() here but we want to configure other attributes
 * so we depend on @WebServlet in {@AppUIVaadinServlet} being discovered.
 *
 * Add helper message so we report what is expected and there is a reference back to here
 */
log.info("Created %s without direct Servlet so %s should be automatically discovered",
		context.getClass().getSimpleName(), AppUIVaadinServlet.class.getSimpleName());

context.setDisplayName(CONSOLE_DISPLAY_NAME);
context.setBaseResource(findWebRoot());
context.setContextPath(AppProductConfig.CONSOLE_CONTEXT_ROOT);
context.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", ".*\\.jar|.*/classes/.*");
context.setConfigurationDiscovered(true);
context.setConfigurations(new Configuration[]{
		new AnnotationConfiguration(),
		new WebInfConfiguration(),
		new WebXmlConfiguration(),
		new MetaInfConfiguration(),
		new FragmentConfiguration(),
		new EnvConfiguration(),
		new PlusConfiguration(),
		new JettyWebXmlConfiguration()
});
context.getServletContext().setExtendedListenerTypes(true);
context.addEventListener(new ServletContextListeners());
context.addEventListener(new ConsoleServletContextListener());
try {
	// fixes IllegalStateException: Unable to configure jsr356 at that stage. ServerContainer is null
	WebSocketServerContainerInitializer.initialize(context);
} catch (ServletException e) {
	throw new WrappedException(e);
}

The above code is run with system property vaadin.productionMode=true

Versions:
Vaadin 14.2.3
Jetty 9.4.27.v20200227

We are still stuck and can’t figure this out so far. Would really appreciate some help on this.

Here’s a bit more of the output:

2020-07-07 10:45:07.393 [main]
 INFO  Started o.e.j.s.ServletContextHandler@68b5a37d{CA Bridge,/cabridge,null,AVAILABLE}
2020-07-07 10:45:07.799 [main]
 INFO  Scanning elapsed time=288ms
2020-07-07 10:45:07.814 [main]
 INFO  NO JSP Support for /console, did not find org.eclipse.jetty.jsp.JettyJspServlet
2020-07-07 10:45:07.830 [main]
 DEBUG Unable to fully determine correct flow-build-info.
Accepting file 'file:/C:/Program%20Files/AAA/NNN/lib/WebApp-6.0.7.jar!/META-INF/VAADIN/config/flow-build-info.json'
2020-07-07 10:45:07.861 [main]
 DEBUG Skipping DEV MODE because PRODUCTION MODE is set.
2020-07-07 10:45:07.877 [main]
 INFO  Initializing AtmosphereFramework
2020-07-07 10:45:07.892 [main]
 DEBUG Unable to fully determine correct flow-build-info.
Accepting file 'file:/C:/Program%20Files/AAA/NNN/lib/WebApp-6.0.7.jar!/META-INF/VAADIN/config/flow-build-info.json'
2020-07-07 10:45:07.892 [main]
 DEBUG Unable to fully determine correct flow-build-info.
Accepting file 'file:/C:/Program%20Files/AAA/NNN/lib/WebApp-6.0.7.jar!/META-INF/VAADIN/config/flow-build-info.json'
2020-07-07 10:45:07.892 [main]
 INFO  Skipping automatic servlet registration because there are no navigation targets registered to the route registry and there are no web component exporters.
2020-07-07 10:45:07.908 [main]
 DEBUG Executing contextInitialized
2020-07-07 10:45:07.908 [main]
 DEBUG Atmosphere available, initializing
2020-07-07 10:45:07.908 [main]
 DEBUG Checking if default is a Vaadin Servlet
2020-07-07 10:45:07.908 [main]
 DEBUG Checking if jsp is a Vaadin Servlet

I did some debugging and found that when RouteRegistryInitializer.process() is called the “classSet” argument is an empty Set (see below). This later leads the vaadin servlet to report there are “no navigation targets registered”. So jetty is not providing any of my View (@Route) classes to process(). Not sure why.

My app has multiple jar files and I’ve verified the jar with the @Route classes is in the classpath and that the jar has all the @Route classes in it.

@HandlesTypes({ Route.class, RouteAlias.class })
public class RouteRegistryInitializer extends AbstractRouteRegistryInitializer
        implements ClassLoaderAwareServletContainerInitializer {

    @Override
    public void process(Set<Class<?>> classSet, ServletContext servletContext)
            throws ServletException {
        VaadinServletContext context = new VaadinServletContext(servletContext);
        try {
            if (classSet == null) {
                ApplicationRouteRegistry routeRegistry = ApplicationRouteRegistry
                        .getInstance(context);
                routeRegistry.clean();
                return;
            }

            Set<Class<? extends Component>> routes = validateRouteClasses(
                    classSet.stream());

            ApplicationRouteRegistry routeRegistry = ApplicationRouteRegistry
                    .getInstance(context);

            RouteConfiguration routeConfiguration = RouteConfiguration
                    .forRegistry(routeRegistry);
            routeConfiguration.update(
                    () -> setAnnotatedRoutes(routeConfiguration, routes));
            routeRegistry.setPwaConfigurationClass(validatePwaClass(
                    routes.stream().map(clazz -> (Class<?>) clazz)));
        } catch (InvalidRouteConfigurationException irce) {
            throw new ServletException(
                    "Exception while registering Routes on servlet startup",
                    irce);
        }
    }

I have found the problem and fixed it. In our product we have multiple sub-projects. The Main class is part of a project which constructs all the servlets and starts jetty. Those classes are in a single “main.jar” file which is explicitly in the runtime java CLASSPATH. The actual Vaadin code is in a separate project and is built into its own “ui.jar” which is placed in the $install/lib directory. The $install/lib dir is in $CLASSPATH.

When jetty scans for @Route classes it scans main.jar because its in $CLASSPATH directly. It never scans ui.jar because its not directly in $CLASSPATH.

I tried experimenting with this:

context.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", ".*\\.jar|.*/lib/.*")

but that didn’t work. Jetty still never scanned ui.jar.

What did work was

context.setExtraClasspath("C:/Program Files/VVV/NNN/lib/ui.jar")

This got jetty to scan ui.jar and find the @Route classes.

What an easy and fun problem :frowning: