This is a follow-up to #15844. As asked by @philwebb, attached is an example project.

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.boot.loader.jar.Handler (file:/Users/dschulz/IdeaProjects/testproject/build/libs/demo-0.0.1-SNAPSHOT.jar) to constructor sun.net.www.protocol.jar.Handler()
WARNING: Please consider reporting this to the maintainers of org.springframework.boot.loader.jar.Handler
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

Issue description

This warning is issued reproducibly when running fat jars (built with (mvn|gradle) bootJar) using springdoc-openapi dependency in Spring Boot projects. The warning is issued when accessing the URL path associated to Swagger UI provided by springdoc-openapi.

Steps to reproduce

unzip testproject.zip && cd testproject
./gradlew bootJar
java -jar build/libs/demo-0.0.1-SNAPSHOT.jar  

Now point a browser to http://localhost:8080 or explicitly http://localhost:8080/swagger-ui.html.

Tested with:

  • Spring Boot versions: 2.1.9-RELEASE, 2.2.0.RELEASE
  • Java version: 13
java -version
openjdk version "13" 2019-09-17
OpenJDK Runtime Environment (build 13+33)
OpenJDK 64-Bit Server VM (build 13+33, mixed mode, sharing)

Comment From: wilkinsona

Thanks for the sample, @dschulz. I've reproduced the problem. It's caused by a combination of increased reflective access restrictions in JDK 13 and Tomcat's servlet context resource handling which causes Spring Boot's jar URL handler to try to fall back to the JDK's. This fall back requires the use of reflection and this is now restricted in JDK 13.

While ugly, the failure is benign on JDK 13. It can be avoided by switching to Jetty or swapped for another warning (XNIO-330) by switching to Undertow.

I don't think it's possible to fall back to the JDK's handler without using reflection so I think we'll need to rework our handler so that it copes with Tomcat's */-separated jar:war:file URLs.

Comment From: wilkinsona

When illegal access is denied (--illegal-access=deny), accessing the Swagger UI fails with the following exception:

java.io.IOException: Unable to open root Jar file 'war:file:/Users/awilkinson/Downloads/testproject/build/libs/demo-0.0.1-SNAPSHOT.jar*/BOOT-INF/lib/swagger-ui-3.23.5.jar'
    at org.springframework.boot.loader.jar.Handler.getRootJarFile(Handler.java:316) ~[demo-0.0.1-SNAPSHOT.jar:na]
    at org.springframework.boot.loader.jar.Handler.getRootJarFileFromUrl(Handler.java:298) ~[demo-0.0.1-SNAPSHOT.jar:na]
    at org.springframework.boot.loader.jar.Handler.openConnection(Handler.java:84) ~[demo-0.0.1-SNAPSHOT.jar:na]
    at java.base/java.net.URL.openConnection(URL.java:1086) ~[na:na]
    at org.springframework.core.io.AbstractFileResolvingResource.contentLength(AbstractFileResolvingResource.java:239) ~[spring-core-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.springframework.web.servlet.resource.ResourceHttpRequestHandler.setHeaders(ResourceHttpRequestHandler.java:686) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.springframework.web.servlet.resource.ResourceHttpRequestHandler.handleRequest(ResourceHttpRequestHandler.java:488) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter.handle(HttpRequestHandlerAdapter.java:53) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar!/:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.27.jar!/:9.0.27]
    at java.base/java.lang.Thread.run(Thread.java:830) ~[na:na]
Caused by: java.lang.IllegalStateException: Not a file URL
    at org.springframework.boot.loader.jar.Handler.getRootJarFile(Handler.java:304) ~[demo-0.0.1-SNAPSHOT.jar:na]
    ... 47 common frames omitted

Comment From: wilkinsona

Here is a possible fix. It uses a different approach for falling back when the URL is a jar:war: URL created by Tomcat. It's really hard to test so one existing test has just been tightened up to verify that the existing fall back is still used for jar:file: URLs. I'd like some more eyes on this before we consider merging it. Could you take a look please, @philwebb?

Comment From: JaquelineP

Any updates?

Comment From: derTobsch

We are seeing the same warning on Spring Boot Version 2.4.0 with jdk11 on the https://github.com/synyx/urlaubsverwaltung/ project see https://github.com/synyx/urlaubsverwaltung/issues/1545

Comment From: philwebb

The original sample provided no longer shows the warning. I think this is due to this Tomcat update.

Comment From: philwebb

@derTobsch Can you provide instructions on how to replicate the problem with your project? Perhaps it only happens with war packaged applications now.

Comment From: derTobsch

fyi: we are now on Spring Boot 2.4.1.

  1. Clone the repo - git@github.com:synyx/urlaubsverwaltung.git
  2. ./mvnw clean package in the root
  3. docker-compose up for mariaDB and mailhog
  4. Start the application war via java -jar -Dspring.profiles.active=demodata target/urlaubsverwaltung-4.4.0-SNAPSHOT.war

and the warning will occur see

➜  urlaubsverwaltung git:(master) java -jar -Dspring.profiles.active=demodata target/urlaubsverwaltung-4.4.0-SNAPSHOT.war                             
Urlaubsverwaltung
4.4.0-SNAPSHOT
          ////       ////////       ////
        /::::://////////////////////:::::/
       //::::::////////////////////:::::://
        //:::://////////////////////:::://
         ////////////////////////////////
         ////////////////////////////////
      /------:::::////////////:::::------/
   ....-:::::::::---..------..---:::::::::-....
    :.://///////////:........://///////////:.:
     .://////////////-.://:.-//////////////:.
     .-/////////////:.:////:.//////////////--
     :.:///////////:.:hhddhh:-:///////////:.:
    //:.-:::////::-.+o:----:++.-::////:::-.://
   //////:---...-:shdo......odhs:-...---://////
   //////////////oddddhs++ohdddds//////////////
  ///////////////+ddddddddddddddo///////////////
  ////////////////yddddddddddddy////////////////
   ////////////////ohddddddddho////////////////
    /////////////////+oossoo+/////////////////
     ////////////////////////////////////////
       ////////////////////////////////////
         ////////////////////////////////
           ////////////////////////////
               ////////////////////
                    //////////

based on Spring Boot version 2.4.1

2020-12-17 10:22:14.563  INFO 3354701 --- [           main] o.s.u.UrlaubsverwaltungApplication       : Starting UrlaubsverwaltungApplication v4.4.0-SNAPSHOT using Java 11.0.2 on turing with PID 3354701 (/home/schneider/projects/urlaubsverwaltung/urlaubsverwaltung/target/urlaubsverwaltung-4.4.0-SNAPSHOT.war started by schneider in /home/schneider/projects/urlaubsverwaltung/urlaubsverwaltung)
2020-12-17 10:22:14.584  INFO 3354701 --- [           main] o.s.u.UrlaubsverwaltungApplication       : The following profiles are active: demodata
2020-12-17 10:22:18.352  INFO 3354701 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-12-17 10:22:18.649  INFO 3354701 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 288 ms. Found 18 JPA repository interfaces.
2020-12-17 10:22:19.641  INFO 3354701 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler@5328a9c1' of type [org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-12-17 10:22:19.652  INFO 3354701 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'methodSecurityMetadataSource' of type [org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-12-17 10:22:20.442  INFO 3354701 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-12-17 10:22:20.461  INFO 3354701 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-12-17 10:22:20.462  INFO 3354701 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.41]
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.boot.loader.jar.Handler (file:/home/--/projects/urlaubsverwaltung/urlaubsverwaltung/target/urlaubsverwaltung-4.4.0-SNAPSHOT.war) to constructor sun.net.www.protocol.jar.Handler()
WARNING: Please consider reporting this to the maintainers of org.springframework.boot.loader.jar.Handler
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
...

I hope this will help you. :-)

Comment From: philwebb

The error is caused by a similar Tomcat URL jar:war:file:/Users/pwebb/projects/spring-boot/samples/gh-18631/urlaubsverwaltung/target/urlaubsverwaltung-4.4.0-SNAPSHOT.war*/WEB-INF/lib/jstl-1.2.jar!/META-INF/c-1_0-rt.tld.

This time, the CachedResourceURLStreamHandler doesn't get involved, so we see the warning:

Here's the stack trace:

Thread [main] (Suspended (breakpoint at line 129 in Handler))   
    owns: TomcatEmbeddedContext  (id=111)   
    owns: StandardHost  (id=112)    
    owns: StandardEngine  (id=113)  
    owns: StandardService  (id=114) 
    owns: Object  (id=115)  
    owns: StandardServer  (id=116)  
    owns: Object  (id=117)  
    owns: Object  (id=118)  
    Handler.getFallbackHandler() line: 129  
    Handler.openFallbackConnection(URL, Exception) line: 101    
    Handler.openConnection(URL) line: 89    
    URL.openConnection() line: 1101 
    URL.openStream() line: 1167 
    TldResourcePath.openStream() line: 127  
    TldParser.parse(TldResourcePath) line: 61   
    TldScanner.parseTld(TldResourcePath) line: 275  
    TldScanner$TldScannerCallback.scan(Jar, String, boolean) line: 315  
    StandardJarScanner.process(JarScanType, JarScannerCallback, URL, String, boolean, Deque<URL>) line: 387 
    StandardJarScanner.scan(JarScanType, ServletContext, JarScannerCallback) line: 195  
    TldScanner.scanJars() line: 262 
    TldScanner.scan() line: 104 
    JasperInitializer.onStartup(Set<Class<?>>, ServletContext) line: 83 
    TomcatEmbeddedContext(StandardContext).startInternal() line: 5166   
    TomcatEmbeddedContext(LifecycleBase).start() line: 183  
    ContainerBase$StartChild.call() line: 1384  
    ContainerBase$StartChild.call() line: 1374  
    FutureTask<V>.run() line: 264   
    InlineExecutorService.execute(Runnable) line: 75    
    InlineExecutorService(AbstractExecutorService).submit(Callable<T>) line: 140    
    StandardHost(ContainerBase).startInternal() line: 909   
    StandardHost.startInternal() line: 843  
    StandardHost(LifecycleBase).start() line: 183   
    ContainerBase$StartChild.call() line: 1384  
    ContainerBase$StartChild.call() line: 1374  
    FutureTask<V>.run() line: 264   
    InlineExecutorService.execute(Runnable) line: 75    
    InlineExecutorService(AbstractExecutorService).submit(Callable<T>) line: 140    
    StandardEngine(ContainerBase).startInternal() line: 909 
    StandardEngine.startInternal() line: 262    
    StandardEngine(LifecycleBase).start() line: 183 
    StandardService.startInternal() line: 434   
    StandardService(LifecycleBase).start() line: 183    
    StandardServer.startInternal() line: 930    
    StandardServer(LifecycleBase).start() line: 183 
    Tomcat.start() line: 486    
    TomcatWebServer.initialize() line: 123  
    TomcatWebServer.<init>(Tomcat, boolean, Shutdown) line: 104 
    TomcatServletWebServerFactory.getTomcatWebServer(Tomcat) line: 451  
    TomcatServletWebServerFactory.getWebServer(ServletContextInitializer...) line: 200  
    AnnotationConfigServletWebServerApplicationContext(ServletWebServerApplicationContext).createWebServer() line: 181  
    AnnotationConfigServletWebServerApplicationContext(ServletWebServerApplicationContext).onRefresh() line: 159    
    AnnotationConfigServletWebServerApplicationContext(AbstractApplicationContext).refresh() line: 582  
    AnnotationConfigServletWebServerApplicationContext(ServletWebServerApplicationContext).refresh() line: 144  
    SpringApplication.refresh(ConfigurableApplicationContext) line: 767 
    SpringApplication.refresh(ApplicationContext) line: 759 
    SpringApplication.refreshContext(ConfigurableApplicationContext) line: 426  
    SpringApplication.run(String...) line: 326  
    SpringApplication.run(Class<?>[], String[]) line: 1309  
    SpringApplication.run(Class<?>, String...) line: 1298   
    UrlaubsverwaltungApplication.main(String[]) line: 16    
    NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]  
    NativeMethodAccessorImpl.invoke(Object, Object[]) line: 64  
    DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43  
    Method.invoke(Object, Object...) line: 564  
    MainMethodRunner.run() line: 49 
    WarLauncher(Launcher).launch(String[], String, ClassLoader) line: 107   
    WarLauncher(Launcher).launch(String[]) line: 58 
    WarLauncher.main(String[]) line: 59 

Comment From: philwebb

Tomcat's URLs are interesting and there's probably an opportunity to replace them with our own nested JAR handling.

They take the form jar:war:file:/some.war*/WEB-INF/lib/some.jar!/entry. With a regular JDK the jar Handler is used. It takes the war:file:/some.war*/WEB-INF/lib/some.jar!/entry part an tries to read the jar file. The sun.net.www.protocol.jar.URLJarFile class is responsible getting the actual JarFile.

Since the URL is war: and not file: the getJarFile(...) method needs to do a full retrieval. This means the content is copied to a temp file so that it can be accessed.

Comment From: philwebb

@wilkinsona Sorry it took over a year to get to review this 😱

I think I've found another way to solve it that will work with all fallback URLs. I've pushed something in c4e41305d54cc076ae8c9713d1f118cf65b25e90.

Comment From: derTobsch

that is nice to hear @philwebb - thanks! :)