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
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.
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.
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)
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.
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
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?
Any updates?
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
The original sample provided no longer shows the warning. I think this is due to this Tomcat update.
@derTobsch Can you provide instructions on how to replicate the problem with your project? Perhaps it only happens with war packaged applications now.
fyi: we are now on Spring Boot 2.4.1.
[email protected]:synyx/urlaubsverwaltung.git./mvnw clean package in the rootdocker-compose up for mariaDB and mailhogStart 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. :-)
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
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.
@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.
that is nice to hear @philwebb - thanks! :)
Most helpful comment
@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.