Now ladies and gentlemen! It is time for a debugging break in the series of articles about the REST service!
Actually, this article is related to the REST service – it will describe a problem with the classpath-scanning of Jersey that manifest itself in standalone Spring Boot applications with a so-called fat JAR. I will also suggest a workaround that avoids scanning of the classpath.
The code snippets in this article were taken from the REST example program that can be found on GitHub.
Jersey Configuration
This is the Jersey configuration that, in my standalone Spring Boot application, will cause the problem to manifest:
package se.ivankrizsan.restexample.restadapter; import org.glassfish.jersey.server.ResourceConfig; import org.springframework.stereotype.Component; /** * Jersey configuration. * * @author Ivan Krizsan */ @Component public class JerseyConfig extends ResourceConfig { public JerseyConfig() { /* All resource classes are to be located in the same package as this class. */ packages(this.getClass().getPackage().getName()); } }
Build the Standalone Application
If I build the application using mvn clean package and then go into the target directory of the project, I see the following:
total 68064 drwxr-xr-x 10 ivan staff 340 Dec 6 07:32 . drwxr-xr-x 14 ivan staff 476 Dec 6 07:32 .. drwxr-xr-x 4 ivan staff 136 Dec 6 07:32 classes drwxr-xr-x 3 ivan staff 102 Dec 6 07:32 generated-sources drwxr-xr-x 3 ivan staff 102 Dec 6 07:32 generated-test-sources drwxr-xr-x 3 ivan staff 102 Dec 6 07:32 maven-archiver drwxr-xr-x 3 ivan staff 102 Dec 6 07:32 maven-status -rw-r--r-- 1 ivan staff 34820625 Dec 6 07:32 rest-example-0.0.1-SNAPSHOT.jar -rw-r--r-- 1 ivan staff 23331 Dec 6 07:32 rest-example-0.0.1-SNAPSHOT.jar.original drwxr-xr-x 4 ivan staff 136 Dec 6 07:32 test-classes
In my case the standalone Spring Boot application is in the largest JAR file, the rest-example-0.0.1-SNAPSHOT.jar.
Exceptions Galore
Trying to run the standalone Spring Boot application, using java -jar rest-example-0.0.1-SNAPSHOT.jar, the error below will occur. I have deleted all but the last parts of the stacktrace.
Caused by: org.glassfish.jersey.server.internal.scanning.ResourceFinderException: java.io.FileNotFoundException: /Volumes/HD/DEVELOPMENT/GIT-REPOS/rest-example/target/rest-example-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes (No such file or directory) at org.glassfish.jersey.server.internal.scanning.JarZipSchemeResourceFinderFactory.create(JarZipSchemeResourceFinderFactory.java:89) ~[jersey-server-2.24.jar!/:na] at org.glassfish.jersey.server.internal.scanning.JarZipSchemeResourceFinderFactory.create(JarZipSchemeResourceFinderFactory.java:65) ~[jersey-server-2.24.jar!/:na] at org.glassfish.jersey.server.internal.scanning.PackageNamesScanner.addResourceFinder(PackageNamesScanner.java:282) ~[jersey-server-2.24.jar!/:na] at org.glassfish.jersey.server.internal.scanning.PackageNamesScanner.init(PackageNamesScanner.java:198) ~[jersey-server-2.24.jar!/:na] at org.glassfish.jersey.server.internal.scanning.PackageNamesScanner.<init>(PackageNamesScanner.java:154) ~[jersey-server-2.24.jar!/:na] at org.glassfish.jersey.server.internal.scanning.PackageNamesScanner.<init>(PackageNamesScanner.java:110) ~[jersey-server-2.24.jar!/:na] at org.glassfish.jersey.server.ResourceConfig.packages(ResourceConfig.java:680) ~[jersey-server-2.24.jar!/:na] at org.glassfish.jersey.server.ResourceConfig.packages(ResourceConfig.java:660) ~[jersey-server-2.24.jar!/:na] at se.ivankrizsan.restexample.restadapter.JerseyConfig.<init>(JerseyConfig.java:16) ~[classes!/:0.0.1-SNAPSHOT] at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_111] at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_111] at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_111] at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_111] at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:145) ~[spring-beans-5.0.0.M3.jar!/:5.0.0.M3] ... 49 common frames omitted Caused by: java.io.FileNotFoundException: /Volumes/HD/DEVELOPMENT/GIT-REPOS/rest-example/target/rest-example-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes (No such file or directory) at java.io.FileInputStream.open0(Native Method) ~[na:1.8.0_111] at java.io.FileInputStream.open(FileInputStream.java:195) ~[na:1.8.0_111] at java.io.FileInputStream.<init>(FileInputStream.java:138) ~[na:1.8.0_111] at java.io.FileInputStream.<init>(FileInputStream.java:93) ~[na:1.8.0_111] at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:90) ~[na:1.8.0_111] at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:188) ~[na:1.8.0_111] at java.net.URL.openStream(URL.java:1045) ~[na:1.8.0_111] at org.glassfish.jersey.server.internal.scanning.JarZipSchemeResourceFinderFactory.getInputStream(JarZipSchemeResourceFinderFactory.java:177) ~[jersey-server-2.24.jar!/:na] at org.glassfish.jersey.server.internal.scanning.JarZipSchemeResourceFinderFactory.create(JarZipSchemeResourceFinderFactory.java:87) ~[jersey-server-2.24.jar!/:na] ... 62 common frames omitted
The reason for this error is a bug in Jersey. The JIRA issue for the bug can be found here. On the Spring side, there is two year old issue here.
There is a suggested workaround for Spring Boot which proposes unpacking JAR-files that contain classes that are to be scanned by Jersey. However, if you are in my situation where the package to be scanned is part of your own application and you, like me, don’t quite feel like extracting those classes to a separate JAR-file I will propose another solution.
New Jersey Configuration
To avoid having Jersey to perform a classpath scan, the resource classes, in my case, can be registered explicitly. The new JerseyConfig class in my REST example program project looks like this:
package se.ivankrizsan.restexample.restadapter; import org.glassfish.jersey.server.ResourceConfig; import org.springframework.stereotype.Component; /** * Jersey configuration. * Cannot rely on classpath scanning, due to a bug in Jersey making it unable * to scan nested JAR-files. * * @author Ivan Krizsan */ @Component public class JerseyConfig extends ResourceConfig { public JerseyConfig() { register(CircleResource.class); register(RectangleResource.class); register(DrawingResource.class); } }
Rinse and Rebuild
Rebuild the application using mvn clean package and you should now be able to run the Spring Boot standalone JAR without the previous error.
Happy coding!
Thats awesome — thanks a lot! This solution is really much more helpful then the (cumbersome) one with unpackaging JARS
Cool. Clear description. Thanks for the suggestion.
You are Freaking Genius !!