I am running a Spring Boot 3.0.3 web application with an embedded Apache Tomcat 10.1.6 container on JDK 17. Spring Boot 3.0.3 officially supports Tomcat 10.1.5 and I decided to upgrade to experiment. My application ultimately produces a WAR file and can be run as an executable WAR file using the likes of "java -jar...". When the app runs, the following exception shows up: Caused by: java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory$LoaderHidingResourceRoot@17b37e9a] at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122) at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191) at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:878) ... 37 more Caused by: org.apache.catalina.LifecycleException: Failed to start component [org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory$LoaderHidingResourceRoot@17b37e9a] at org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:440) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:198) at org.apache.catalina.core.StandardContext.resourcesStart(StandardContext.java:4566) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4699) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1332) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1322) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) 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:871) ... 37 more Caused by: java.lang.NullPointerException: Cannot invoke "java.net.URL.getProtocol()" because "url" is null at org.apache.catalina.webresources.StandardRoot$BaseLocation.<init>(StandardRoot.java:815) at org.apache.catalina.webresources.StandardRoot.createWebResourceSet(StandardRoot.java:357) at org.apache.catalina.webresources.StandardRoot.processWebInfLib(StandardRoot.java:588) at org.apache.catalina.webresources.StandardRoot.startInternal(StandardRoot.java:721) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ... 46 more If it helps, you can also see this failure in action here: https://github.com/mmoayyed/cas/actions/runs/4269310213/jobs/7432837455 This is not the case with Apache Tomcat 10.1.5 (which is what Spring Boot 3.0.3 officially supports as of this writing) I was not able to exactly pinpoint what might have changed, but debugging through the code and following stack-trace I am able to track it down to this block: protected void processWebInfLib() throws LifecycleException { WebResource[] possibleJars = listResources("/WEB-INF/lib", false); for (WebResource possibleJar : possibleJars) { if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) { createWebResourceSet(ResourceSetType.CLASSES_JAR, "/WEB-INF/classes", possibleJar.getURL(), "/"); } } } The issue stems from the fact that "possibleJar.getURL()" returns null. The getURL() method does the following: @Override public URL getURL() { String url = baseUrl + URLEncoder.DEFAULT.encode(resource.getName(), StandardCharsets.UTF_8); try { return new URI(url).toURL(); } catch (MalformedURLException | URISyntaxException e) { if (getLog().isDebugEnabled()) { getLog().debug(sm.getString("fileResource.getUrlFail", url), e); } return null; } } The "url" that is processed above has the prefix "war:", which I think is causing this failure. Please note that deploy the web application into an standalone external tomcat distribution works OK. This appears to mainly be an issue with the embedded tomcat and WARs.
Likely related to this change since the WAR protocol isn't being picked up: Switch to using the ServiceLoader mechanism to load the custom URL protocol handlers that Tomcat uses. (markt) I thought the embedded JAR had the necessary META-INF/services entry. Let me check that.
It looks to be missing in my local build. Need to figure out why as the necessary plumbing appears to be in place in the build script.
Thank you for the follow up! If I can help with anything (ie. testing), please let me know.
Found it. The BND configuration for OSGI needed to be configured to retain the ServiceLoader config file for the war: protocol. The CI system should generate an updated 10.1.7-SNAPSHOT in a couple of hours which you can pick up from: https://repository.apache.org/content/groups/snapshots/ If you can let us know if that fixes the problem that would be very helpful.
Thanks much. I ran some tests against 10.1.7-SNAPSHOT and I am still seeing the failure. Same stacktrace, etc. When you get a chance, could you please confirm whether SNAPSHOTs are published already? I made sure my version of 10.1.7-SNAPSHOT was recent via the likes of Maven's -U.
Re-opening... The JAR file needs to be this one or later: /home/mark/Downloads/tomcat-embed-core-10.1.7-20230226.182604-9.jar The other way to check is that the JAR should have a META-INF/services directory containing a file called java.net.spi.URLStreamHandlerProvider If you have the right JAR and still see the issue, please created the simplest possible test case that demonstrates the issue and I'll take a look.
Thank you. The JAR that I have is simply named 'tomcat-embed-core-10.1.7-SNAPSHOT'. I did verify that the JAR does have a META-INF/services directory containing a file called java.net.spi.URLStreamHandlerProvider, which looks correct based on your reference. I'll work on a simple reproducer shortly.
Please see https://github.com/mmoayyed/tomcat-10.1.7-bug-reproducer for a reproducer. I added instructions in the readme to note how one might build and run the app. This may not be the simplest demo per se, but I think it should serve us well in demonstrating the issue. If you run into problems running the sample, please let me know and I'll help where I can. Thank you!
Thanks for the test case. I can see what is happening. The private static method URL.providers() does a ServiceLoader lookup but using the system class loader. Hence it doesn't see the custom provider for "war" as tomcat-embed-core.jar is loaded by a child class loader. I'm currently looking at options to fix this.
Fixed in: - 11.0.x for 11.0.0-M4 onwards - 10.1.x for 10.1.7 onwards 9.0.x and earlier were not affected.
Confirming that the fix does indeed remove the problem. Thanks again!