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.