Bug 66491 - NullPointerException when resolving webapp JARs as resources
Summary: NullPointerException when resolving webapp JARs as resources
Status: RESOLVED FIXED
Alias: None
Product: Tomcat 10
Classification: Unclassified
Component: Catalina (show other bugs)
Version: 10.1.6
Hardware: PC Mac OS X 10.1
: P2 normal (vote)
Target Milestone: ------
Assignee: Tomcat Developers Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2023-02-25 17:18 UTC by Misagh Moayyed
Modified: 2023-02-28 13:09 UTC (History)
0 users



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Misagh Moayyed 2023-02-25 17:18:23 UTC
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.
Comment 1 Mark Thomas 2023-02-25 18:03:38 UTC
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.
Comment 2 Mark Thomas 2023-02-25 18:17:53 UTC
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.
Comment 3 Misagh Moayyed 2023-02-26 09:02:30 UTC
Thank you for the follow up! If I can help with anything (ie. testing), please let me know.
Comment 4 Mark Thomas 2023-02-26 17:45:18 UTC
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.
Comment 5 Misagh Moayyed 2023-02-26 20:34:50 UTC
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.
Comment 6 Mark Thomas 2023-02-27 09:12:57 UTC
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.
Comment 7 Misagh Moayyed 2023-02-27 09:28:12 UTC
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.
Comment 8 Misagh Moayyed 2023-02-27 09:59:43 UTC
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!
Comment 9 Mark Thomas 2023-02-27 15:24:11 UTC
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.
Comment 10 Mark Thomas 2023-02-27 17:20:10 UTC
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.
Comment 11 Misagh Moayyed 2023-02-28 13:09:11 UTC
Confirming that the fix does indeed remove the problem. Thanks again!