Jersey and Spring Boot Standalone JAR Files

By | December 6, 2016

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.

Spring Boot Jersey

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!

3 thoughts on “Jersey and Spring Boot Standalone JAR Files

  1. Dee

    Thats awesome — thanks a lot! This solution is really much more helpful then the (cumbersome) one with unpackaging JARS

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *