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)
- Gradle version: 5.6.2
- Test project: testproject.zip
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.
- Clone the repo -
git@github.com:synyx/urlaubsverwaltung.git
./mvnw clean package
in the rootdocker-compose up
for mariaDB and mailhog- 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! :)