SpringBoot version 2.1.8.RELEASE

Please refer to jetty issue https://github.com/eclipse/jetty.project/issues/4103. See also StackOverflow reports of same problem for Tomcat: https://stackoverflow.com/questions/44389716/spring-boot-embedded-tomcat-weblistener-scanned-by-servletcomponentscan

The issue is that a ServletContextListener that is annotated with @WebListener is not able to programmatically add Servlets (and I suppose Filters, but I didn't test that), but throws an UnsupportedOperationException. Here's an example of such an exception for jetty:

org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Jetty web server
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:156) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-5.1.9.RELEASE.jar!/:5.1.9.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1204) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    at com.example.demo.DemoApplication.main(DemoApplication.java:13) ~[classes!/:0.0.1-SNAPSHOT]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
    at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48) ~[demo-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:87) ~[demo-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:51) ~[demo-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
    at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52) ~[demo-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Jetty web server
    at org.springframework.boot.web.embedded.jetty.JettyWebServer.initialize(JettyWebServer.java:114) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    at org.springframework.boot.web.embedded.jetty.JettyWebServer.<init>(JettyWebServer.java:86) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    at org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory.getJettyWebServer(JettyServletWebServerFactory.java:398) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    at org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory.getWebServer(JettyServletWebServerFactory.java:153) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:180) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:153) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
    ... 16 common frames omitted
Caused by: java.lang.UnsupportedOperationException: null
    at org.eclipse.jetty.servlet.ServletContextHandler$Context.checkDynamic(ServletContextHandler.java:1088) ~[jetty-servlet-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.servlet.ServletContextHandler$Context.addServlet(ServletContextHandler.java:1245) ~[jetty-servlet-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at com.example.demo.MyServletContextListener.contextInitialized(MyServletContextListener.java:16) ~[classes!/:0.0.1-SNAPSHOT]
    at org.eclipse.jetty.server.handler.ContextHandler.callContextInitialized(ContextHandler.java:930) ~[jetty-server-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.servlet.ServletContextHandler.callContextInitialized(ServletContextHandler.java:555) ~[jetty-servlet-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.server.handler.ContextHandler.startContext(ContextHandler.java:889) ~[jetty-server-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:357) ~[jetty-servlet-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.webapp.WebAppContext.startWebapp(WebAppContext.java:1443) ~[jetty-webapp-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1407) ~[jetty-webapp-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:822) ~[jetty-server-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:276) ~[jetty-servlet-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:524) ~[jetty-webapp-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:72) ~[jetty-util-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:169) ~[jetty-util-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.server.Server.start(Server.java:407) ~[jetty-server-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:110) ~[jetty-util-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:106) ~[jetty-server-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.server.Server.doStart(Server.java:371) ~[jetty-server-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:72) ~[jetty-util-9.4.21-SNAPSHOT.jar!/:9.4.21-SNAPSHOT]
    at org.springframework.boot.web.embedded.jetty.JettyWebServer.initialize(JettyWebServer.java:108) ~[spring-boot-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]

This problem occurs because springboot is processing the @WebListener annotation and then using the ServletContext programmatic interfaces to add the listener: this classifies the listener as being programmatically added, and thus not permitted to add servlets/filters/listeners in its contextInitialized() method. See the ServletSpecification section 4.4:

If the ServletContext passed to the ServletContextListener’s
contextInitialized method where the ServletContextListener was neither
declared in web.xml or web-fragment.xml nor annotated with @WebListener
then an UnsupportedOperationException MUST be thrown for all the methods
defined in ServletContext for programmatic configuration of servlets, filters and
listeners.

Here is the springboot stacktrace showing where the @WebListener is being added as a programmatic listener:

    at org.eclipse.jetty.server.handler.ContextHandler.addProgrammaticListener(ContextHandler.java:718)
    at org.eclipse.jetty.servlet.ServletContextHandler.access$000(ServletContextHandler.java:89)
    at org.eclipse.jetty.servlet.ServletContextHandler$Context.addListener(ServletContextHandler.java:1439)
    at org.springframework.boot.web.servlet.ServletListenerRegistrationBean.register(ServletListenerRegistrationBean.java:116)
    at org.springframework.boot.web.servlet.RegistrationBean.onStartup(RegistrationBean.java:53)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:228)
    at org.springframework.boot.web.embedded.jetty.ServletContextInitializerConfiguration.callInitializers(ServletContextInitializerConfiguration.java:65)
    at org.springframework.boot.web.embedded.jetty.ServletContextInitializerConfiguration.configure(ServletContextInitializerConfiguration.java:54)
    at org.eclipse.jetty.webapp.WebAppContext.configure(WebAppContext.java:498)
    at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1402)
    at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:822)
    at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:276)
    at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:524)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:72)
    at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:169)
    at org.eclipse.jetty.server.Server.start(Server.java:407)
    at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:110)
    at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:106)
    at org.eclipse.jetty.server.Server.doStart(Server.java:371)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:72)
    at org.springframework.boot.web.embedded.jetty.JettyWebServer.initialize(JettyWebServer.java:108)
    at org.springframework.boot.web.embedded.jetty.JettyWebServer.<init>(JettyWebServer.java:86)
    at org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory.getJettyWebServer(JettyServletWebServerFactory.java:398)
    at org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory.getWebServer(JettyServletWebServerFactory.java:153)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:180)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:153)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:312)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1204)
    at com.example.demo.DemoApplication.main(DemoApplication.java:13)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:51)
    at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52)

Comment From: wilkinsona

Thanks, @janbartel. This is a known limitation at the moment due to the way that Spring Boot scans for and registers @WebListener-annotated components. Is there an API in Jetty to register @WebListener-annotated components programmatically that would still allow them to perform their own programmatic registration of further components?

Comment From: janbartel

@wilkinsona the way our own handling of @WebListener annotations adds a listener, using jetty-specific apis, is as follows:

ListenerHolder h = _context.getServletHandler().newListenerHolder(new Source(Source.Origin.ANNOTATION, clazz.getName()));
 h.setHeldClass(clazz);
_context.getServletHandler().addListener(h);

Where _context is a WebAppContext.

Comment From: larsgrefer

For JoinFaces we've implemented a WebServerFactoryCustomizer which is able to add listeners to the embedded Jetty, Tomcat or Undertow in such a way that they aren't affected by the restrictions of Section 4.4 of the ServletSpec: https://github.com/joinfaces/joinfaces/blob/master/joinfaces-autoconfigure/src/main/java/org/joinfaces/autoconfigure/servlet/WebFragmentRegistrationBean.java

@wilkinsona Could the implementation behind @ServletComponentScan use this in order to remove this "known limitation"?

Comment From: wilkinsona

I have a proposal for this but there is a small chance that it will be a breaking change. As such, I'm wonder if it may be a little too risky to do it in 2.2.x.

Comment From: wilkinsona

The proposal requires some further thought as there are some package tangles at the moment.

Comment From: wilkinsona

I've reworked the changes to remove the package tangles: https://github.com/wilkinsona/spring-boot/tree/gh-18303.